programing

Bash에서 파일 경로를 어떻게 정규화합니까?

new-time 2020. 5. 17. 21:57
반응형

Bash에서 파일 경로를 어떻게 정규화합니까?


나는 변환 할

/foo/bar/..

/foo

이것을 수행하는 bash 명령이 있습니까?


편집 : 실제 경우에는 디렉토리가 존재합니다.


경로에서 파일 이름의 일부를 잘라내려면 "dirname"과 "basename"이 친구이며 "realpath"도 편리합니다.

dirname /foo/bar/baz 
# /foo/bar 
basename /foo/bar/baz
# baz
dirname $( dirname  /foo/bar/baz  ) 
# /foo 
realpath ../foo
# ../foo: No such file or directory
realpath /tmp/../tmp/../tmp
# /tmp

realpath 대안

 

realpath

쉘에서 지원하지 않는 경우 시도해 볼 수 있습니다

readlink -f /path/here/.. 

또한

readlink -m /path/there/../../ 

와 동일하게 작동

realpath -s /path/here/../../

경로를 정규화하기 위해 존재할 필요가 없다는 점에서


이 작업을 수행하는 직접 bash 명령이 있는지 모르겠지만 일반적으로 수행합니다.

normalDir="`cd "${dirToNormalize}";pwd`"
echo "${normalDir}"

잘 작동합니다.


시도하십시오

realpath

. 아래는 공개 소스에 기증 된 전체 소스입니다.

// realpath.c: display the absolute path to a file or directory.
// Adam Liss, August, 2007
// This program is provided "as-is" to the public domain, without express or
// implied warranty, for any non-profit use, provided this notice is maintained.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>   
#include <limits.h>

static char *s_pMyName;
void usage(void);

int main(int argc, char *argv[])
{
    char
        sPath[PATH_MAX];


    s_pMyName = strdup(basename(argv[0]));

    if (argc < 2)
        usage();

    printf("%s\n", realpath(argv[1], sPath));
    return 0;
}    

void usage(void)
{
    fprintf(stderr, "usage: %s PATH\n", s_pMyName);
    exit(1);
}

이식성이 뛰어나고 안정적인 솔루션은 파이썬을 사용하는 것입니다. 파이썬은 다윈을 포함하여 거의 모든 곳에 사전 설치되어 있습니다. 두 가지 옵션이 있습니다.

  1. abspath

    절대 경로를 반환하지만 심볼릭 링크를 확인하지 않습니다.

    python -c "import os,sys; print os.path.abspath(sys.argv[1])" path/to/file

  2. realpath

    절대 경로를 반환하면 심볼릭 링크가 해결되어 표준 경로가 생성됩니다.

    python -c "import os,sys; print os.path.realpath(sys.argv[1])" path/to/file

각각의 경우

path/to/file

상대 또는 절대 경로 수 있습니다.


coreutils 패키지의 readlink 유틸리티를 사용하십시오.

MY_PATH=$(readlink -f "$0")

readlink

절대 경로를 얻기위한 bash 표준입니다. 또한 경로 또는 경로가 존재하지 않는 경우 빈 플래그를 반환하는 이점이 있습니다 (플래그가 제공됨).존재하거나 존재하지 않을 수 있지만 부모가있는 디렉토리에 대한 절대 경로를 얻으려면 다음을 사용하십시오.

abspath=$(readlink -f $path)

모든 부모와 함께 존재해야하는 디렉토리의 절대 경로를 얻으려면 다음을 수행하십시오.

abspath=$(readlink -e $path)

주어진 경로를 정식화하고 존재하는 경우 심볼릭 링크를 따르지만 누락 된 디렉토리를 무시하고 경로를 반환하려면 다음과 같습니다.

abspath=$(readlink -m $path)

유일한 단점은 readlink가 링크를 따른다는 것입니다. 링크를 따르지 않으려면 다음 대체 규칙을 사용할 수 있습니다.

abspath=$(cd ${path%/*} && echo $PWD/${path##*/})

$ path의 디렉토리 부분으로 chdir하고 $ path의 파일 부분과 함께 현재 디렉토리를 인쇄합니다. chdir에 실패하면 빈 문자열과 stderr에 오류가 발생합니다.


오래된 질문이지만 쉘 수준에서

전체 경로 이름

처리 하는 경우 훨씬 간단한 방법 이 있습니다 .

   abspath = "$ (cd"$ path "&& pwd)"

CD는 서브 쉘에서 발생하므로 기본 스크립트에 영향을 미치지 않습니다.쉘 내장 명령이 -L 및 -P를 허용한다고 가정하면 다음과 같은 두 가지 변형이 있습니다.

   abspath = "$ (cd -P"$ path "&& pwd -P)" "# 심볼릭 링크가 해결 된 물리적 경로
   abspath = "$ (cd -L"$ path "&& pwd -L)" "# 논리적 경로 보존 심볼릭 링크

개인적으로, 어떤 이유로 든 상징적 인 링크에 매료되지 않으면 나중에이 접근법이 거의 필요하지 않습니다.참고 : 스크립트가 현재 디렉토리를 나중에 변경하더라도 작동하는 스크립트의 시작 디렉토리를 얻는 변형.

name0 = "$ (기본 이름"$ 0 ")"; # 기본 스크립트 이름
dir0 = "$ (cd"$ (dirname "$ 0") ""&& pwd) "; # 절대 시작 디렉토리

CD를 사용하면 스크립트가 ./script.sh와 같은 명령으로 실행 되더라도 cd / pwd없이 종종 단지 ..를 제공하더라도 절대 디렉토리를 항상 확보 할 수 있습니다. 스크립트가 나중에 CD를 수행하는 경우에는 쓸모가 없습니다.


내 최근 해결책은 다음과 같습니다.

pushd foo/bar/..
dir=`pwd`
popd

Tim Whitcomb의 답변을 바탕으로합니다.


Adam Liss가 언급했듯이

realpath

모든 배포판에 번들로 제공되는 것은 아닙니다. 그것이 최고의 솔루션이기 때문에 부끄러운 일입니다. 제공된 소스 코드가 훌륭하므로 지금 사용하기 시작할 것입니다. 여기까지 내가 지금까지 사용해 왔던 내용이 있습니다.

get_abs_path() {
     local PARENT_DIR=$(dirname "$1")
     cd "$PARENT_DIR"
     local ABS_PATH="$(pwd)"/"$(basename "$1")"
     cd - >/dev/null
     echo "$ABS_PATH"
} 

당신이 해결의 심볼릭 링크를 원하는 경우에, 다만 교체

pwd

와 함께

pwd -P

.


정확히 대답은 아니지만 후속 질문 일 수도 있습니다 (원래 질문은 명시 적이 지 않음).

readlink

실제로 심볼릭 링크를 따르고 싶다면 괜찮습니다. 그러나 단순히 정상화에 대한 사용 사례도있다

./

../

//

순수 구문 적으로 수행 할 수 있습니다 시퀀스

없이

심볼릭 링크를 정규화는.

readlink

이것에 좋지 않으며 둘 다 아닙니다

realpath

.

for f in $paths; do (cd $f; pwd); done

기존 경로에서는 작동하지만 다른 경로에서는 작동하지 않습니다.

sed

(스크립트는 반복적으로 시퀀스를 대체 할 수 있다는 점을 제외하고, 좋은 베팅이 될 것 같다

/foo/bar/baz/../..

->

/foo/bar/..

> -

/foo

모든 시스템에 가정, 또는 어떤 추한 루프를 사용하여 출력 비교하는 것은 안전하지 않습니다 펄, 같은 것을 사용하지 않고)

sed

로를 입력.Java (JDK 6+)를 사용하는 단일 라이너 FWIW :

jrunscript -e 'for (var i = 0; i < arguments.length; i++) {println(new java.io.File(new java.io.File(arguments[i]).toURI().normalize()))}' $paths

말이 많고 약간 늦었습니다. 오래된 RHEL4 / 5에 붙어 있기 때문에 하나를 작성해야합니다. 절대 및 상대 링크를 처리하고 //, /./ 및 somedir /../ 항목을 단순화합니다.

test -x /usr/bin/readlink || readlink () {
        echo $(/bin/ls -l $1 | /bin/cut -d'>' -f 2)
    }


test -x /usr/bin/realpath || realpath () {
    local PATH=/bin:/usr/bin
    local inputpath=$1
    local changemade=1
    while [ $changemade -ne 0 ]
    do
        changemade=0
        local realpath=""
        local token=
        for token in ${inputpath//\// }
        do 
            case $token in
            ""|".") # noop
                ;;
            "..") # up one directory
                changemade=1
                realpath=$(dirname $realpath)
                ;;
            *)
                if [ -h $realpath/$token ] 
                then
                    changemade=1
                    target=`readlink $realpath/$token`
                    if [ "${target:0:1}" = '/' ]
                    then
                        realpath=$target
                    else
                        realpath="$realpath/$target"
                    fi
                else
                    realpath="$realpath/$token"
                fi
                ;;
            esac
        done
        inputpath=$realpath
    done
    echo $realpath
}

mkdir -p /tmp/bar
(cd /tmp ; ln -s /tmp/bar foo; ln -s ../.././usr /tmp/bar/link2usr)
echo `realpath /tmp/foo`

나는 파티에 늦었지만 다음과 같이 많은 스레드를 읽은 후에 만들어진 솔루션입니다.

resolve_dir() {
        (builtin cd `dirname "${1/#~/$HOME}"`'/'`basename "${1/#~/$HOME}"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)
}

이것은 $ 1의 절대 경로를 해결하고 ~로 잘 플레이하고 경로에 symlinks를 유지하며 디렉토리 스택을 망칠 수 없습니다. 전체 경로를 반환하거나 존재하지 않는 경우 아무것도 반환하지 않습니다. $ 1은 디렉토리가 될 것으로 예상하고 그렇지 않으면 실패 할 것입니다.하지만 쉽게 확인할 수 있습니다.


GitHub에 배치 한 무료 Bash 라이브러리 제품인

realpath-lib

를 무료로 사용해보십시오 . 철저하게 문서화되어 있으며 훌륭한 학습 도구를 만듭니다.로컬, 상대 및 절대 경로를 해결하고 Bash 4+를 제외한 종속성이 없습니다. 어디에서나 작동합니다. 무료이며 깨끗하고 단순하며 유익합니다.넌 할 수있어:

get_realpath <absolute|relative|symlink|local file path>

이 기능은 라이브러리의 핵심입니다.

function get_realpath() {

if [[ -f "$1" ]]
then 
    # file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
    then 
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else 
        # file *must* be local
        local tmppwd="$PWD"
    fi
else 
    # file *cannot* exist
    return 1 # failure
fi

# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success

}

It also contains functions to get_dirname, get_filename, get_ stemname and validate_path. Try it across platforms, and help to improve it.


Based on @Andre's answer, I might have a slightly better version, in case someone is after a loop-free, completely string-manipulation based solution. It is also useful for those who don't want to dereference any symlinks, which is the downside of using realpath or readlink -f.

It works on bash versions 3.2.25 and higher.

shopt -s extglob

normalise_path() {
    local path="$1"
    # get rid of /../ example: /one/../two to /two
    path="${path//\/*([!\/])\/\.\./}"
    # get rid of /./ and //* example: /one/.///two to /one/two
    path="${path//@(\/\.\/|\/+(\/))//}"
    # remove the last '/.'
    echo "${path%%/.}"
}

$ normalise_path /home/codemedic/../codemedic////.config
/home/codemedic/.config

The problem with realpath is that it is not available on BSD (or OSX for that matter). Here is a simple recipe extracted from a rather old (2009) article from Linux Journal, that is quite portable:

function normpath() {
  # Remove all /./ sequences.
  local path=${1//\/.\//\/}

  # Remove dir/.. sequences.
  while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
    path=${path/${BASH_REMATCH[0]}/}
  done
  echo $path
}

Notice this variant also does not require the path to exist.


Based on loveborg's excellent python snippet, I wrote this:

#!/bin/sh

# Version of readlink that follows links to the end; good for Mac OS X

for file in "$@"; do
  while [ -h "$file" ]; do
    l=`readlink $file`
    case "$l" in
      /*) file="$l";;
      *) file=`dirname "$file"`/"$l"
    esac
  done
  #echo $file
  python -c "import os,sys; print os.path.abspath(sys.argv[1])" "$file"
done

FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

This works even if the file doesn't exist. It does require the directory containing the file to exist.


I needed a solution that would do all three:

  • Work on a stock Mac. realpath and readlink -f are addons
  • Resolve symlinks
  • Have error handling

None of the answers had both #1 and #2. I added #3 to save others any further yak-shaving.

#!/bin/bash

P="${1?Specify a file path}"

[ -e "$P" ] || { echo "File does not exist: $P"; exit 1; }

while [ -h "$P" ] ; do
    ls="$(ls -ld "$P")"
    link="$(expr "$ls" : '.*-> \(.*\)$')"
    expr "$link" : '/.*' > /dev/null &&
        P="$link" ||
        P="$(dirname "$P")/$link"
done
echo "$(cd "$(dirname "$P")"; pwd)/$(basename "$P")"

Here is a short test case with some twisted spaces in the paths to fully exercise the quoting

mkdir -p "/tmp/test/ first path "
mkdir -p "/tmp/test/ second path "
echo "hello" > "/tmp/test/ first path / red .txt "
ln -s "/tmp/test/ first path / red .txt " "/tmp/test/ second path / green .txt "

cd  "/tmp/test/ second path "
fullpath " green .txt "
cat " green .txt "

I know this is an ancient question. I'm still offering an alternative. Recently I met the same issue and found no existing and portable command to do that. So I wrote the following shell script which includes a function that can do the trick.

#! /bin/sh                                                                                                                                                

function normalize {
  local rc=0
  local ret

  if [ $# -gt 0 ] ; then
    # invalid
    if [ "x`echo $1 | grep -E '^/\.\.'`" != "x" ] ; then
      echo $1
      return -1
    fi

    # convert to absolute path
    if [ "x`echo $1 | grep -E '^\/'`" == "x" ] ; then
      normalize "`pwd`/$1"
      return $?
    fi

    ret=`echo $1 | sed 's;/\.\($\|/\);/;g' | sed 's;/[^/]*[^/.]\+[^/]*/\.\.\($\|/\);/;g'`
  else
    read line
    normalize "$line"
    return $?
  fi

  if [ "x`echo $ret | grep -E '/\.\.?(/|$)'`" != "x" ] ; then
    ret=`normalize "$ret"`
    rc=$?
  fi

  echo "$ret"
  return $rc
}

https://gist.github.com/bestofsong/8830bdf3e5eb9461d27313c3c282868c


I discovered today that you can use the stat command to resolve paths.

So for a directory like "~/Documents":

You can run this:

stat -f %N ~/Documents

To get the full path:

/Users/me/Documents

For symlinks, you can use the %Y format option:

stat -f %Y example_symlink

Which might return a result like:

/usr/local/sbin/example_symlink

The formatting options might be different on other versions of *NIX but these worked for me on OSX.


A simple solution using node.js:

#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));

참고URL : https://stackoverflow.com/questions/284662/how-do-you-normalize-a-file-path-in-bash

반응형