source

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

ittop 2023. 5. 12. 22:43
반응형

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

나는 변신하고 싶습니다./foo/bar/../foo

이를 수행하는 bash 명령어가 있습니까?


편집: 실제로는 디렉터리가 존재합니다.

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

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")

오래된 질문이지만 셸 수준에서 전체 경로 이름을 처리하는 경우 훨씬간단한 방법이 있습니다.

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 )", #절대 시작 dir

CD를 사용하면 스크립트가 ./script와 같은 명령으로 실행되는 경우에도 항상 절대 디렉터리를 사용할 수 있습니다.cd/pwd 없이 종종 그냥 주는 sh.스크립트가 나중에 cd를 수행하면 사용할 수 없습니다.

readlink절대 경로를 얻기 위한 bash 표준입니다.또한 경로 또는 경로가 존재하지 않는 경우 빈 문자열을 반환할 수 있는 이점이 있습니다(이를 위해 플래그가 지정됨).

존재하거나 존재하지 않지만 존재하는 부모가 있는 디렉토리에 대한 절대 경로를 가져오려면 다음을 사용합니다.

abspath=$(readlink -f $path)

모든 부모와 함께 존재해야 하는 디렉토리에 대한 절대 경로를 가져오려면:

abspath=$(readlink -e $path)

지정된 경로를 표준화하고 심볼릭 링크가 존재하는 경우 심볼릭 링크를 따르지만 그렇지 않으면 누락된 디렉토리를 무시하고 경로만 반환하는 것은 다음과 같습니다.

abspath=$(readlink -m $path)

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

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

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

아담 리스가 언급한 바야흐로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.

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

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

팀 휘트컴의 답변을 토대로 합니다.

정확한 답변은 아니지만 후속 질문일 수 있습니다(원래 질문은 명시적이지 않았습니다.

readlink심볼릭 링크를 실제로 따르고 싶다면 괜찮습니다.하지만 단순히 정상화하는 것에 대한 사용 사례도 있습니다../그리고.../그리고.//심볼릭 링크를 표준화하지 않고 순수하게 구문적으로 수행할 수 있는 시퀀스. readlink이것에 좋지 않고, 또한 좋지 않습니다.realpath.

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

기존 경로에서는 작동하지만 다른 경로에서는 중단됩니다.

A sed시퀀스를 으로 바꿀 수 인 것 ./foo/bar/baz/../..->/foo/bar/..->/foo에 안전하지 Perl과 하거나 일부 하여 의 sed입력한 대로

FWIW, Java(JDK 6+)를 사용하는 원라이너:

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

파티에 늦었지만 다음과 같은 내용을 읽고 해결책을 마련했습니다.

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

이렇게 하면 $1의 절대 경로가 해결되고 ~와 함께 잘 작동하며 심볼릭 링크를 있는 경로에 유지하면 디렉터리 스택이 손상되지 않습니다.전체 경로를 반환하거나 경로가 없으면 아무것도 반환하지 않습니다.1달러가 디렉터리가 될 것으로 예상하고 그렇지 않으면 실패할 수도 있지만, 직접 확인하는 것은 쉬운 일입니다.

말도 많고, 대답도 좀 늦었습니다.저는 오래된 RHEL4/5에 갇혀 있기 때문에 하나를 써야 합니다.절대 및 상대 링크를 처리하고 //, // 및 일부 dir/../ 항목을 단순화합니다.

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`

realpath즉, BSD(또는 해당 OSX)에서는 사용할 수 없습니다.다음은 리눅스 저널의 다소 오래된(2009년) 기사에서 발췌한 간단한 레시피로, 휴대성이 매우 뛰어납니다.

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

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

또한 이 변형은 경로가 필요하지 않습니다.

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

}

또한 get_dirname, get_filename, get_stemname 및 validate_path 함수도 포함되어 있습니다.여러 플랫폼에서 사용해 보고 개선을 지원합니다.

@Andre의 답변에 따르면, 루프가 없는 완전한 문자열 조작 기반 솔루션을 원하는 사람이 있을 경우를 대비하여 조금 더 나은 버전을 사용할 수 있습니다.를 사용하지 심볼릭 링크를 사용하는 의 단점입니다.realpath또는readlink -f.

bash 버전 3.2.25 이상에서 작동합니다.

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

저는 (재미를 위해) 최대한의 성능에 중점을 두고 이를 처리하기 위해 내장된 기능을 만들었습니다.심볼릭 링크를 해결하지 않으므로 기본적으로 다음과 같습니다.realpath -sm.

## A bash-only mimic of `realpath -sm`. 
## Give it path[s] as argument[s] and it will convert them to clean absolute paths
abspath () { 
  ${*+false} && { >&2 echo $FUNCNAME: missing operand; return 1; };
  local c s p IFS='/';  ## path chunk, absolute path, input path, IFS for splitting paths into chunks
  local -i r=0;         ## return value

  for p in "$@"; do
    case "$p" in        ## Check for leading backslashes, identify relative/absolute path
    '') ((r|=1)); continue;;
    //[!/]*)  >&2 echo "paths =~ ^//[^/]* are impl-defined; not my problem"; ((r|=2)); continue;;
    /*) ;;
    *)  p="$PWD/$p";;   ## Prepend the current directory to form an absolute path
    esac

    s='';
    for c in $p; do     ## Let IFS split the path at '/'s
      case $c in        ### NOTE: IFS is '/'; so no quotes needed here
      ''|.) ;;          ## Skip duplicate '/'s and '/./'s
      ..) s="${s%/*}";; ## Trim the previous addition to the absolute path string
      *)  s+=/$c;;      ### NOTE: No quotes here intentionally. They make no difference, it seems
      esac;
    done;

    echo "${s:-/}";     ## If xpg_echo is set, use `echo -E` or `printf $'%s\n'` instead
  done
  return $r;
}

참고:이는 참: 이는다음시로작으로 .//경로의 시작 부분에 정확히 두 개의 이중 슬래시가 구현 정의 동작이기 때문입니다.하지만, 그것은 처리합니다./,///기타 등등 아주 좋습니다.

이 기능은 모든 에지 케이스를 제대로 처리하는 것처럼 보이지만, 아직 처리하지 못한 부분이 있을 수 있습니다.

참고:인수와 될 때, 능성참수: 개고인함수호때께출될와의,abspath보다 약 10배 더 느리게 실행됩니다.realpath -sm때, 하의주장호출때될로으나,abspath보다 110배 이상 빠르게 실행됩니다.realpath -sm대부분 매번 새로운 프로그램을 실행할 필요가 없기 때문에 내 컴퓨터에서.

파일 시스템을 건드리지 않고, 링크를 해결하지 않고, 외부 유틸리티를 사용하지 않고, 존재하거나 존재하지 않는 경로를 정규화하려면 Python에서 번역한 순수 Bash 함수가 여기 있습니다.posixpath.normpath.

#!/usr/bin/env bash

# Normalize path, eliminating double slashes, etc.
# Usage: new_path="$(normpath "${old_path}")"
# Translated from Python's posixpath.normpath:
# https://github.com/python/cpython/blob/master/Lib/posixpath.py#L337
normpath() {
  local IFS=/ initial_slashes='' comp comps=()
  if [[ $1 == /* ]]; then
    initial_slashes='/'
    [[ $1 == //* && $1 != ///* ]] && initial_slashes='//'
  fi
  for comp in $1; do
    [[ -z ${comp} || ${comp} == '.' ]] && continue
    if [[ ${comp} != '..' || (-z ${initial_slashes} && ${#comps[@]} -eq 0) || (\
      ${#comps[@]} -gt 0 && ${comps[-1]} == '..') ]]; then
      comps+=("${comp}")
    elif ((${#comps[@]})); then
      unset 'comps[-1]'
    fi
  done
  comp="${initial_slashes}${comps[*]}"
  printf '%s\n' "${comp:-.}"
}

예:

new_path="$(normpath '/foo/bar/..')"
echo "${new_path}"
# /foo

normpath "relative/path/with trailing slashs////"
# relative/path/with trailing slashs

normpath "////a/../lot/././/mess////./here/./../"
# /lot/mess

normpath ""
# .
# (empty path resolved to dot)

개인적으로 파일을 조작하는 데 자주 사용되는 언어인 셸이 경로를 처리하는 기본 기능을 제공하지 않는 이유를 이해할 수 없습니다.파이썬에는 os.path나 pathlib와 같은 훌륭한 라이브러리가 있는데, 이 라이브러리는 파일 이름, 확장자, 기본 이름, 경로 세그먼트, 분할 또는 조인 경로를 추출하고, 절대 또는 정규화된 경로를 얻고, 경로 간의 관계를 결정하고, 많은 두뇌 없이 모든 것을 수행할 수 있는 모든 도구를 제공합니다.그리고 그들은 가장자리 케이스를 처리하고 신뢰할 수 있습니다.셸에서는 이러한 작업을 수행하기 위해 외부 실행 파일이라고 부르거나 매우 기본적이고 난해한 구문을 사용하여 휠을 재창조해야 합니다.

다음 세 가지를 모두 수행할 수 있는 솔루션이 필요했습니다.

  • . 맥에서 일하세요.realpath그리고.readlink -f 자료
  • 심볼 링크 확인
  • 오류 처리가 있습니다.

1번과 2번 모두 정답이 없었습니다.저는 다른 사람들을 더 이상 야크 쉐이빙하지 않기 위해 3번을 추가했습니다.

#!/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")"

다음은 견적을 충분히 발휘할 수 있도록 경로에 약간의 공간이 비뚤어진 짧은 테스트 사례입니다.

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 "

loveborg의 훌륭한 파이썬 스니펫을 기반으로, 저는 다음과 같이 썼습니다.

#!/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)

파일이 없는 경우에도 작동합니다.파일이 들어 있는 디렉터리가 있어야 합니다.

이것이 오래된 질문이라는 것을 압니다.저는 여전히 대안을 제시하고 있습니다.최근에 같은 문제가 발생하여 이를 수행할 기존의 휴대용 명령어를 찾을 수 없었습니다.그래서 저는 트릭을 할 수 있는 기능을 포함한 다음과 같은 셸 스크립트를 작성했습니다.

#! /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

제시된 솔루션 중 저에게 맞는 솔루션이 없었기 때문에, 파일이 존재하지 않는 경우에 저는 제 아이디어를 구현했습니다.André Anjos의 솔루션은 .../../로 시작하는 경로가 잘못 해결되는 문제를 가지고 있었습니다.예를 들어 ....../a/b/가 a/b/가 되었습니다.

function normalize_rel_path(){
  local path=$1
  result=""
  IFS='/' read -r -a array <<< "$path"
  i=0
  for (( idx=${#array[@]}-1 ; idx>=0 ; idx-- )) ; do
    c="${array[idx]}"
    if [ -z "$c" ] || [[ "$c" == "." ]];
    then
      continue
    fi
    if [[ "$c" == ".." ]]
    then
      i=$((i+1))
    elif [ "$i" -gt "0" ];
    then
      i=$((i-1))
    else
      if [ -z "$result" ];
      then
        result=$c
      else
        result=$c/$result
      fi
    fi
  done
  while [ "$i" -gt "0" ]; do
    i=$((i-1))
    result="../"$result
  done  
  unset IFS
  echo $result
}

나는 오늘 당신이 사용할 수 있다는 것을 발견했습니다.stat경로를 확인하는 명령입니다.

그래서 "~/Documents"와 같은 디렉토리의 경우:

다음을 실행할 수 있습니다.

stat -f %N ~/Documents

전체 경로를 가져오는 방법

/Users/me/Documents

심볼릭 링크의 경우 %Y 형식 옵션을 사용할 수 있습니다.

stat -f %Y example_symlink

다음과 같은 결과가 반환될 수 있습니다.

/usr/local/sbin/example_symlink

*NIX의 다른 버전에서는 포맷 옵션이 다를 수 있지만 OSX에서는 이러한 옵션이 작동했습니다.

다을사용간솔루션단한한을 사용한 간단한 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

반응형