[bash] Bash를 사용하여 현재 디렉토리에서 절대 경로를 상대 경로로 변환

예:

absolute="/foo/bar"
current="/foo/baz/foo"

# Magic

relative="../../bar"

마술을 만들려면 어떻게해야합니까?



답변

GNU coreutils 8.23의 realpath를 사용하는 것이 가장 간단합니다.

$ realpath --relative-to="$file1" "$file2"

예를 들면 다음과 같습니다.

$ realpath --relative-to=/usr/bin/nmap /tmp/testing
../../../tmp/testing


답변

$ python -c "import os.path; print os.path.relpath('/foo/bar', '/foo/baz/foo')"

제공합니다 :

../../bar


답변

이것은 @pini의 현재 최고 등급의 솔루션을 수정하여 완전히 기능적으로 개선 한 것입니다 (슬프게도 소수의 경우 만 처리).

알림 : 문자열이 길이가 0 인 경우 ‘-z’테스트 (= 빈) 및 문자열이 비어 있지 않은 경우 ‘-n’테스트

# both $1 and $2 are absolute paths beginning with /
# returns relative path to $2/$target from $1/$source
source=$1
target=$2

common_part=$source # for now
result="" # for now

while [[ "${target#$common_part}" == "${target}" ]]; do
    # no match, means that candidate common part is not correct
    # go up one level (reduce common part)
    common_part="$(dirname $common_part)"
    # and record that we went back, with correct / handling
    if [[ -z $result ]]; then
        result=".."
    else
        result="../$result"
    fi
done

if [[ $common_part == "/" ]]; then
    # special case for root (no common path)
    result="$result/"
fi

# since we now have identified the common part,
# compute the non-common part
forward_part="${target#$common_part}"

# and now stick all parts together
if [[ -n $result ]] && [[ -n $forward_part ]]; then
    result="$result$forward_part"
elif [[ -n $forward_part ]]; then
    # extra slash removal
    result="${forward_part:1}"
fi

echo $result

테스트 사례 :

compute_relative.sh "/A/B/C" "/A"           -->  "../.."
compute_relative.sh "/A/B/C" "/A/B"         -->  ".."
compute_relative.sh "/A/B/C" "/A/B/C"       -->  ""
compute_relative.sh "/A/B/C" "/A/B/C/D"     -->  "D"
compute_relative.sh "/A/B/C" "/A/B/C/D/E"   -->  "D/E"
compute_relative.sh "/A/B/C" "/A/B/D"       -->  "../D"
compute_relative.sh "/A/B/C" "/A/B/D/E"     -->  "../D/E"
compute_relative.sh "/A/B/C" "/A/D"         -->  "../../D"
compute_relative.sh "/A/B/C" "/A/D/E"       -->  "../../D/E"
compute_relative.sh "/A/B/C" "/D/E/F"       -->  "../../../D/E/F"


답변

#!/bin/bash
# both $1 and $2 are absolute paths
# returns $2 relative to $1

source=$1
target=$2

common_part=$source
back=
while [ "${target#$common_part}" = "${target}" ]; do
  common_part=$(dirname $common_part)
  back="../${back}"
done

echo ${back}${target#$common_part/}


답변

2001 년 부터 Perl에 내장되어 있으므로 VMS 까지 상상할 수있는 거의 모든 시스템에서 작동합니다 .

perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' FILE BASE

또한 솔루션은 이해하기 쉽습니다.

예를 들어,

perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' $absolute $current

… 잘 작동합니다.


답변

설치된 것으로 가정 : bash, pwd, dirname, echo; relpath는

#!/bin/bash
s=$(cd ${1%%/};pwd); d=$(cd $2;pwd); b=; while [ "${d#$s/}" == "${d}" ]
do s=$(dirname $s);b="../${b}"; done; echo ${b}${d#$s/}

나는 pini 의 대답을 골랐다. 와 다른 몇 가지 아이디어

참고 : 두 경로 모두 기존 폴더 여야합니다. 파일이 작동 하지 않습니다 .


답변

os.path.relpath쉘 함수로서의 파이썬

relpath연습 의 목표는 xni가os.path.relpath 제안한대로 Python 2.7의 기능 (Python 버전 2.6에서 사용 가능하지만 2.7에서만 제대로 작동 함)을 모방하는 것입니다. 입니다. 결과적으로 일부 결과는 다른 답변에서 제공되는 기능과 다를 수 있습니다.

( python -cZSH의 호출 을 기반으로 유효성 검사를 중단하기 때문에 단순히 경로에서 줄 바꿈으로 테스트하지 않았습니다 . 확실히 노력하면 가능합니다.)

Bash의 “매직”과 관련하여 나는 오래 전에 Bash에서 마법을 찾는 것을 포기했지만 이후 ZSH에서 필요한 모든 마법을 찾았습니다.

결과적으로 두 가지 구현을 제안합니다.

첫 번째 구현은 POSIX를 완전히 준수하는 것 입니다. /bin/dashDebian 6.0.6“Squeeze”에서 테스트했습니다 . 또한 /bin/shOS X 10.8.3 에서도 완벽하게 작동합니다. 실제로 Bash 버전 3.2는 POSIX 쉘인 것처럼 보입니다.

두 번째 구현은 경로의 여러 슬래시 및 기타 방해 요소에 대비 한 ZSH 셸 함수입니다. ZSH를 사용할 수있는 경우 #!/usr/bin/env zsh다른 쉘에서 아래에 제시된 스크립트 형식 (예 : shebang ) 으로 호출하더라도 권장 버전 입니다.

마지막으로 다른 답변에서 제공된 테스트 사례 relpath에서 찾은 명령 의 출력을 확인하는 ZSH 스크립트를 작성했습니다 $PATH. ! ? *여기와 거기에 공백과 탭, 문장 부호를 추가하여 테스트에 향신료를 추가 하고 vim-powerline 에서 발견 된 이국적인 UTF-8 문자로 또 다른 테스트를 던졌습니다. .

POSIX 쉘 기능

먼저 POSIX 호환 쉘 기능. 다양한 경로에서 작동하지만 여러 개의 슬래시를 정리하거나 심볼릭 링크를 해결하지는 않습니다.

#!/bin/sh
relpath () {
    [ $# -ge 1 ] && [ $# -le 2 ] || return 1
    current="${2:+"$1"}"
    target="${2:-"$1"}"
    [ "$target" != . ] || target=/
    target="/${target##/}"
    [ "$current" != . ] || current=/
    current="${current:="/"}"
    current="/${current##/}"
    appendix="${target##/}"
    relative=''
    while appendix="${target#"$current"/}"
        [ "$current" != '/' ] && [ "$appendix" = "$target" ]; do
        if [ "$current" = "$appendix" ]; then
            relative="${relative:-.}"
            echo "${relative#/}"
            return 0
        fi
        current="${current%/*}"
        relative="$relative${relative:+/}.."
    done
    relative="$relative${relative:+${appendix:+/}}${appendix#/}"
    echo "$relative"
}
relpath "$@"

ZSH 쉘 기능

이제 더 강력한 zsh버전입니다. 실제 경로 (la)에 대한 인수를 해결하려면 realpath -f(Linux coreutils패키지 에서 사용 가능 ) :a3 행과 4 행을 다음과 같이 바꾸십시오 .:A .

zsh에서 이것을 사용하려면 첫 번째와 마지막 행을 제거하고 $FPATH변수 에있는 디렉토리에 넣으십시오 .

#!/usr/bin/env zsh
relpath () {
    [[ $# -ge 1 ]] && [[ $# -le 2 ]] || return 1
    local target=${${2:-$1}:a} # replace `:a' by `:A` to resolve symlinks
    local current=${${${2:+$1}:-$PWD}:a} # replace `:a' by `:A` to resolve symlinks
    local appendix=${target#/}
    local relative=''
    while appendix=${target#$current/}
        [[ $current != '/' ]] && [[ $appendix = $target ]]; do
        if [[ $current = $appendix ]]; then
            relative=${relative:-.}
            print ${relative#/}
            return 0
        fi
        current=${current%/*}
        relative="$relative${relative:+/}.."
    done
    relative+=${relative:+${appendix:+/}}${appendix#/}
    print $relative
}
relpath "$@"

테스트 스크립트

마지막으로 테스트 스크립트 하나의 옵션, 즉 -v상세 출력을 가능하게합니다.

#!/usr/bin/env zsh
set -eu
VERBOSE=false
script_name=$(basename $0)

usage () {
    print "\n    Usage: $script_name SRC_PATH DESTINATION_PATH\n" >&2
    exit ${1:=1}
}
vrb () { $VERBOSE && print -P ${(%)@} || return 0; }

relpath_check () {
    [[ $# -ge 1 ]] && [[ $# -le 2 ]] || return 1
    target=${${2:-$1}}
    prefix=${${${2:+$1}:-$PWD}}
    result=$(relpath $prefix $target)
    # Compare with python's os.path.relpath function
    py_result=$(python -c "import os.path; print os.path.relpath('$target', '$prefix')")
    col='%F{green}'
    if [[ $result != $py_result ]] && col='%F{red}' || $VERBOSE; then
        print -P "${col}Source: '$prefix'\nDestination: '$target'%f"
        print -P "${col}relpath: ${(qq)result}%f"
        print -P "${col}python:  ${(qq)py_result}%f\n"
    fi
}

run_checks () {
    print "Running checks..."

    relpath_check '/    a   b/å/⮀*/!' '/    a   b/å/⮀/xäå/?'

    relpath_check '/'  '/A'
    relpath_check '/A'  '/'
    relpath_check '/  & /  !/*/\\/E' '/'
    relpath_check '/' '/  & /  !/*/\\/E'
    relpath_check '/  & /  !/*/\\/E' '/  & /  !/?/\\/E/F'
    relpath_check '/X/Y' '/  & /  !/C/\\/E/F'
    relpath_check '/  & /  !/C' '/A'
    relpath_check '/A /  !/C' '/A /B'
    relpath_check '/Â/  !/C' '/Â/  !/C'
    relpath_check '/  & /B / C' '/  & /B / C/D'
    relpath_check '/  & /  !/C' '/  & /  !/C/\\/Ê'
    relpath_check '/Å/  !/C' '/Å/  !/D'
    relpath_check '/.A /*B/C' '/.A /*B/\\/E'
    relpath_check '/  & /  !/C' '/  & /D'
    relpath_check '/  & /  !/C' '/  & /\\/E'
    relpath_check '/  & /  !/C' '/\\/E/F'

    relpath_check /home/part1/part2 /home/part1/part3
    relpath_check /home/part1/part2 /home/part4/part5
    relpath_check /home/part1/part2 /work/part6/part7
    relpath_check /home/part1       /work/part1/part2/part3/part4
    relpath_check /home             /work/part2/part3
    relpath_check /                 /work/part2/part3/part4
    relpath_check /home/part1/part2 /home/part1/part2/part3/part4
    relpath_check /home/part1/part2 /home/part1/part2/part3
    relpath_check /home/part1/part2 /home/part1/part2
    relpath_check /home/part1/part2 /home/part1
    relpath_check /home/part1/part2 /home
    relpath_check /home/part1/part2 /
    relpath_check /home/part1/part2 /work
    relpath_check /home/part1/part2 /work/part1
    relpath_check /home/part1/part2 /work/part1/part2
    relpath_check /home/part1/part2 /work/part1/part2/part3
    relpath_check /home/part1/part2 /work/part1/part2/part3/part4
    relpath_check home/part1/part2 home/part1/part3
    relpath_check home/part1/part2 home/part4/part5
    relpath_check home/part1/part2 work/part6/part7
    relpath_check home/part1       work/part1/part2/part3/part4
    relpath_check home             work/part2/part3
    relpath_check .                work/part2/part3
    relpath_check home/part1/part2 home/part1/part2/part3/part4
    relpath_check home/part1/part2 home/part1/part2/part3
    relpath_check home/part1/part2 home/part1/part2
    relpath_check home/part1/part2 home/part1
    relpath_check home/part1/part2 home
    relpath_check home/part1/part2 .
    relpath_check home/part1/part2 work
    relpath_check home/part1/part2 work/part1
    relpath_check home/part1/part2 work/part1/part2
    relpath_check home/part1/part2 work/part1/part2/part3
    relpath_check home/part1/part2 work/part1/part2/part3/part4

    print "Done with checks."
}
if [[ $# -gt 0 ]] && [[ $1 = "-v" ]]; then
    VERBOSE=true
    shift
fi
if [[ $# -eq 0 ]]; then
    run_checks
else
    VERBOSE=true
    relpath_check "$@"
fi