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


261

예:

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

# Magic

relative="../../bar"

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


7
예를 들어 gcc 상대 경로를 제공하여 소스 경로가 변경 되어도 사용할 수있는 상대 디버그 정보를 생성 할 수 있습니다.
Offirmo

: 이것과 같은 질문은 U & L에 요청했다 unix.stackexchange.com/questions/100918/... . 답변 (@Gilles) 중 하나는 이 문제를 쉽게 해결할 수있는 symlinks 도구를 언급합니다 .
slm

25
단순 : realpath --relative-to=$absolute $current.
kenorb 2009 년

답변:


228

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

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

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

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

7
패키지가 Ubuntu 14.04에서 구식이며 --relative-to 옵션이없는 것이 유감입니다.
kzh

3
우분투 16.04에서 잘 작동
cayhorstmann

7
$ realpath --relative-to="${PWD}" "$file"현재 작업 디렉토리에 상대적인 경로를 원하는 경우에 유용합니다.
dcoles

1
이것은 내부 것들에 대한 올바른 /usr/bin/nmap/-path하지만하지 않는 /usr/bin/nmap에서 : nmap/tmp/testing그것을 만 ../../이 아니라 3 번 ../. 그러나 ..rootfs에서 수행하는 것이 /입니다.
Patrick B.

6
@PatrickB로. 암시, --relative-to=…디렉토리를 예상하고 확인하지 않습니다. 이는 파일에 상대적인 경로를 요청하면 추가 "../"로 끝나는 것을 의미합니다 (이 예 /usr/bin에서는 디렉토리가 거의 또는 전혀 포함되어 있지 않기 때문에 nmap일반적으로 이진 파일
이므로

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

제공합니다 :

../../bar

11
그것은 효과가 있으며 대안이 어리석게 보입니다. 그것은 저에게 보너스입니다 xD
hasvn

31
+1. 알았어, 속임수 ...하지만 너무 사용하지 않는 것이 좋다! relpath(){ python -c "import os.path; print os.path.relpath('$1','${2:-$PWD}')" ; }
MestreLion

4
안타깝게도 이것은 보편적으로 사용할 수 없습니다 : os.path.relpath는 Python 2.6에서 새로 추가되었습니다.
첸 레비

15
@ChenLevy : Python 2.6은 2008 년에 출시되었습니다. 2012 년에 보편적으로 제공되지 않았다고 생각하기는 어렵습니다.
MestreLion

11
python -c 'import os, sys; print(os.path.relpath(*sys.argv[1:]))'가장 자연스럽고 안정적으로 작동합니다.
musiphil

31

이것은 @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"

1
offirmo shell lib github.com/Offirmo/offirmo-shell-lib에 통합되어 있으며 , 기능«OSL_FILE_find_relative_path»(파일«osl_lib_file.sh»)
Offirmo

1
+1. 다음으로 대체 source=$1; target=$2하여 모든 경로 (/로 시작하는 절대 경로가 아닌)를 쉽게 처리 할 수 ​​있습니다.source=$(realpath $1); target=$(realpath $2)
Josh Kelley

2
실제로 실제로는 dirs가 존재한다면, @Josh, 단위 테스트에는 불편 함이 있습니다;) 그러나 실제 사용에서는 realpath를 사용할 수없는 경우 (예 : 표준이 아님 realpath) 권장 source=$(readlink -f $1)됩니다
Offirmo

나는 이것을 정의 $source하고 $target좋아한다.`if [[-e $ 1]]; 그런 다음 source = $ (readlink -f $ 1); 그렇지 않으면 source = $ 1; fi [[-e $ 2]]; 그런 다음 target = $ (readlink -f $ 2); 다른 목표 = $ 2; 이런 식으로, 함수는 가상의 디렉토리뿐만 아니라 실제 / 기존 상대 경로를 처리 할 수 ​​있습니다.
Nathan S. Watson-Haigh

1
@ NathanS.Watson-Haigh 더 나은, 나는 최근에 그냥 그것을 readlink-m수 있는 옵션을 발견 했습니다;)
Offirmo

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

멋진 스크립트-짧고 깨끗합니다. 편집을 적용했습니다 (피어 검토 대기 중) : common_part = $ source / common_part = $ (dirname $ common_part) / echo $ {back} $ {target # $ common_part} 디렉토리 이름 시작시 부적합한 일치로 인해 기존 스크립트가 실패합니다. 예를 들어 "/ foo / bar / baz"와 "/ foo / barsucks / bonk"를 비교할 때. 슬래시를 var로 이동하고 최종 평가 밖으로 나오면 해당 버그가 수정됩니다.
jcwenger 2016 년

3
이 스크립트는 작동하지 않습니다. 간단한 "하나의 디렉토리 다운"테스트에 실패합니다. jcwenger의 편집은 조금 더 잘 작동하지만 추가 "../"를 추가하는 경향이 있습니다.
박사 사람 사람 II

1
후행 "/"가 인수에 있으면 어떤 경우에는 실패합니다. 예를 들어 $ 1 = "$ HOME /"및 $ 2 = "$ HOME / temp"인 경우 "/ home / user / temp /"를 반환하지만 $ 1 = $ HOME 인 경우 상대 경로 "temp"를 올바르게 반환합니다. 따라서 source = $ 1 및 target = $ 2 둘 다 sed를 사용하여 (또는 bash 변수 대체를 사용하여 "정리"될 수 있지만 => source = $ (echo "$ {1}"| sed 's / \ / * $ // ')
michael

1
사소한 개선 : 소스 / 대상을 $ 1 및 $ 2로 직접 설정하는 대신 source = $ (cd $ 1; pwd) target = $ (cd $ 2; pwd)를 수행하십시오. 이 방법으로로 경로를 처리합니다. 그리고 .. 올바르게.
Joseph Garvin

4
최고 투표 답변 임에도 불구하고이 답변에는 많은 제한이 있으므로 많은 다른 답변이 게시됩니다. 다른 답변, 특히 테스트 사례를 표시하는 답변을 참조하십시오. 그리고이 의견을 찬성하십시오!
Offirmo

25

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

... 잘 작동합니다.


3
sayperl에서 로그로 사용할 수 없지만 여기에서 효과적으로 사용할 수 있습니다. perl -MFile::Spec -E 'say File::Spec->abs2rel(@ARGV)'
William Pursell

+1이지만 더 오래된 유사한 답변 을 참조하십시오 (2012 년 2 월). William Pursell 의 관련 의견도 읽어보십시오 . 내 버전은 두 개의 명령 줄입니다 : perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target"perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target" "$origin". 첫 번째 한 줄 perl 스크립트는 하나의 인수를 사용합니다 (원본은 현재 작업 디렉토리 임). 두 번째 한 줄 스크립트는 두 개의 인수를 사용합니다.
olibre

3
그 대답이 받아 들여 져야합니다. perl대답은 여전히 ​​하나의 라이너이지만 거의 모든 곳에서 찾을 수 있습니다.
Dmitry Ginzburg

19

설치된 것으로 가정 : 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 의 대답을 골랐다. 와 다른 몇 가지 아이디어

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


2
이상적인 대답 : / bin / sh와 함께 작동하며 readlink, python, perl이 필요하지 않습니다.-> 라이트 / 임베디드 시스템 또는 Windows bash 콘솔에 적합
Francois

2
불행하게도, 경로가 존재해야하므로 항상 바람직한 것은 아닙니다.
drwatsoncode

경건한 대답. cd-pwd는 내가 생각하는 링크를 해결하기위한 것입니까? 좋은 골프!
Teck-freak

15

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

2
첫 번째 경로가 끝날 때 작동하지 않습니다 /.
Noldorin

12
#!/bin/sh

# Return relative path from canonical absolute dir path $1 to canonical
# absolute dir path $2 ($1 and/or $2 may end with one or no "/").
# Does only need POSIX shell builtins (no external command)
relPath () {
    local common path up
    common=${1%/} path=${2%/}/
    while test "${path#"$common"/}" = "$path"; do
        common=${common%/*} up=../$up
    done
    path=$up${path#"$common"/}; path=${path%/}; printf %s "${path:-.}"
}

# Return relative path from dir $1 to dir $2 (Does not impose any
# restrictions on $1 and $2 but requires GNU Core Utility "readlink"
# HINT: busybox's "readlink" does not support option '-m', only '-f'
#       which requires that all but the last path component must exist)
relpath () { relPath "$(readlink -m "$1")" "$(readlink -m "$2")"; }

위의 쉘 스크립트는 pini 's (Thanks!) 에서 영감을 받았습니다 . 스택 오버플로의 구문 강조 모듈 (적어도 내 미리보기 프레임)에서 버그가 발생합니다. 강조 표시가 잘못된 경우 무시하십시오.

몇 가지 참고 사항 :

  • 코드 길이와 복잡성을 크게 증가시키지 않으면 서 오류 제거 및 코드 개선
  • 사용 편의성을 위해 기능을 기능에 추가
  • Kept는 POSIX와 호환되므로 모든 POSIX 셸에서 작동해야합니다 (Ubuntu Linux 12.04에서 대시, bash 및 zsh로 테스트).
  • 전역 변수를 방해하거나 전역 네임 스페이스를 오염시키지 않도록 로컬 변수 만 사용
  • 두 디렉토리 경로 모두 존재할 필요는 없습니다 (응용 프로그램 요구 사항).
  • 경로 이름에는 공백, 특수 문자, 제어 문자, 백 슬래시, 탭, ', ",?, *, [,] 등이 포함될 수 있습니다.
  • 핵심 기능 "relPath"는 POSIX 셸 내장만을 사용하지만 표준 절대 디렉토리 경로를 매개 변수로 필요로합니다.
  • 확장 함수 "relpath"는 임의의 디렉토리 경로 (상대적이 아닌 비정규)를 처리 할 수 ​​있지만 외부 GNU 핵심 유틸리티 인 "readlink"가 필요합니다.
  • 내장 "echo"를 피하고 대신 두 가지 이유로 내장 "printf"를 사용했습니다.
  • 불필요한 변환을 피하기 위해 경로 이름은 쉘 및 OS 유틸리티에 의해 리턴되고 예상되는대로 사용됩니다 (예 : cd, ln, ls, find, mkdir; 일부 백 슬래시 시퀀스를 해석하는 파이썬의 "os.path.relpath"와 달리)
  • 언급 된 백 슬래시 시퀀스를 제외하고 함수 "relPath"의 마지막 행은 파이썬과 호환되는 경로 이름을 출력합니다.

    path=$up${path#"$common"/}; path=${path%/}; printf %s "${path:-.}"

    마지막 줄은 한 줄씩 바꾸고 단순화 할 수 있습니다

    printf %s "$up${path#"$common"/}"

    나는 후자를 선호하기 때문에

    1. 파일 이름은 relPath에 의해 얻어진 dir 경로에 직접 추가 될 수 있습니다 :

      ln -s "$(relpath "<fromDir>" "<toDir>")<file>" "<fromDir>"
    2. 이 방법으로 만든 동일한 디렉토리의 심볼릭 링크 "./"에는 파일 이름 앞에 못생긴 것이 없습니다 .

  • 오류가 발생하면 linuxball (at) gmail.com으로 연락하여 문제를 해결해 보겠습니다.
  • 회귀 테스트 스위트 추가 (POSIX 셸 호환 가능)

회귀 테스트를위한 코드 목록 (쉘 스크립트에 간단히 추가) :

############################################################################
# If called with 2 arguments assume they are dir paths and print rel. path #
############################################################################

test "$#" = 2 && {
    printf '%s\n' "Rel. path from '$1' to '$2' is '$(relpath "$1" "$2")'."
    exit 0
}

#######################################################
# If NOT called with 2 arguments run regression tests #
#######################################################

format="\t%-19s %-22s %-27s %-8s %-8s %-8s\n"
printf \
"\n\n*** Testing own and python's function with canonical absolute dirs\n\n"
printf "$format\n" \
    "From Directory" "To Directory" "Rel. Path" "relPath" "relpath" "python"
IFS=
while read -r p; do
    eval set -- $p
    case $1 in '#'*|'') continue;; esac # Skip comments and empty lines
    # q stores quoting character, use " if ' is used in path name
    q="'"; case $1$2 in *"'"*) q='"';; esac
    rPOk=passed rP=$(relPath "$1" "$2"); test "$rP" = "$3" || rPOk=$rP
    rpOk=passed rp=$(relpath "$1" "$2"); test "$rp" = "$3" || rpOk=$rp
    RPOk=passed
    RP=$(python -c "import os.path; print os.path.relpath($q$2$q, $q$1$q)")
    test "$RP" = "$3" || RPOk=$RP
    printf \
    "$format" "$q$1$q" "$q$2$q" "$q$3$q" "$q$rPOk$q" "$q$rpOk$q" "$q$RPOk$q"
done <<-"EOF"
    # From directory    To directory           Expected relative path

    '/'                 '/'                    '.'
    '/usr'              '/'                    '..'
    '/usr/'             '/'                    '..'
    '/'                 '/usr'                 'usr'
    '/'                 '/usr/'                'usr'
    '/usr'              '/usr'                 '.'
    '/usr/'             '/usr'                 '.'
    '/usr'              '/usr/'                '.'
    '/usr/'             '/usr/'                '.'
    '/u'                '/usr'                 '../usr'
    '/usr'              '/u'                   '../u'
    "/u'/dir"           "/u'/dir"              "."
    "/u'"               "/u'/dir"              "dir"
    "/u'/dir"           "/u'"                  ".."
    "/"                 "/u'/dir"              "u'/dir"
    "/u'/dir"           "/"                    "../.."
    "/u'"               "/u'"                  "."
    "/"                 "/u'"                  "u'"
    "/u'"               "/"                    ".."
    '/u"/dir'           '/u"/dir'              '.'
    '/u"'               '/u"/dir'              'dir'
    '/u"/dir'           '/u"'                  '..'
    '/'                 '/u"/dir'              'u"/dir'
    '/u"/dir'           '/'                    '../..'
    '/u"'               '/u"'                  '.'
    '/'                 '/u"'                  'u"'
    '/u"'               '/'                    '..'
    '/u /dir'           '/u /dir'              '.'
    '/u '               '/u /dir'              'dir'
    '/u /dir'           '/u '                  '..'
    '/'                 '/u /dir'              'u /dir'
    '/u /dir'           '/'                    '../..'
    '/u '               '/u '                  '.'
    '/'                 '/u '                  'u '
    '/u '               '/'                    '..'
    '/u\n/dir'          '/u\n/dir'             '.'
    '/u\n'              '/u\n/dir'             'dir'
    '/u\n/dir'          '/u\n'                 '..'
    '/'                 '/u\n/dir'             'u\n/dir'
    '/u\n/dir'          '/'                    '../..'
    '/u\n'              '/u\n'                 '.'
    '/'                 '/u\n'                 'u\n'
    '/u\n'              '/'                    '..'

    '/    a   b/å/⮀*/!' '/    a   b/å/⮀/xäå/?' '../../⮀/xäå/?'
    '/'                 '/A'                   'A'
    '/A'                '/'                    '..'
    '/  & /  !/*/\\/E'  '/'                    '../../../../..'
    '/'                 '/  & /  !/*/\\/E'     '  & /  !/*/\\/E'
    '/  & /  !/*/\\/E'  '/  & /  !/?/\\/E/F'   '../../../?/\\/E/F'
    '/X/Y'              '/  & /  !/C/\\/E/F'   '../../  & /  !/C/\\/E/F'
    '/  & /  !/C'       '/A'                   '../../../A'
    '/A /  !/C'         '/A /B'                '../../B'
    '/Â/  !/C'          '/Â/  !/C'             '.'
    '/  & /B / C'       '/  & /B / C/D'        'D'
    '/  & /  !/C'       '/  & /  !/C/\\/Ê'     '\\/Ê'
    '/Å/  !/C'          '/Å/  !/D'             '../D'
    '/.A /*B/C'         '/.A /*B/\\/E'         '../\\/E'
    '/  & /  !/C'       '/  & /D'              '../../D'
    '/  & /  !/C'       '/  & /\\/E'           '../../\\/E'
    '/  & /  !/C'       '/\\/E/F'              '../../../\\/E/F'
    '/home/p1/p2'       '/home/p1/p3'          '../p3'
    '/home/p1/p2'       '/home/p4/p5'          '../../p4/p5'
    '/home/p1/p2'       '/work/p6/p7'          '../../../work/p6/p7'
    '/home/p1'          '/work/p1/p2/p3/p4'    '../../work/p1/p2/p3/p4'
    '/home'             '/work/p2/p3'          '../work/p2/p3'
    '/'                 '/work/p2/p3/p4'       'work/p2/p3/p4'
    '/home/p1/p2'       '/home/p1/p2/p3/p4'    'p3/p4'
    '/home/p1/p2'       '/home/p1/p2/p3'       'p3'
    '/home/p1/p2'       '/home/p1/p2'          '.'
    '/home/p1/p2'       '/home/p1'             '..'
    '/home/p1/p2'       '/home'                '../..'
    '/home/p1/p2'       '/'                    '../../..'
    '/home/p1/p2'       '/work'                '../../../work'
    '/home/p1/p2'       '/work/p1'             '../../../work/p1'
    '/home/p1/p2'       '/work/p1/p2'          '../../../work/p1/p2'
    '/home/p1/p2'       '/work/p1/p2/p3'       '../../../work/p1/p2/p3'
    '/home/p1/p2'       '/work/p1/p2/p3/p4'    '../../../work/p1/p2/p3/p4'

    '/-'                '/-'                   '.'
    '/?'                '/?'                   '.'
    '/??'               '/??'                  '.'
    '/???'              '/???'                 '.'
    '/?*'               '/?*'                  '.'
    '/*'                '/*'                   '.'
    '/*'                '/**'                  '../**'
    '/*'                '/***'                 '../***'
    '/*.*'              '/*.**'                '../*.**'
    '/*.???'            '/*.??'                '../*.??'
    '/[]'               '/[]'                  '.'
    '/[a-z]*'           '/[0-9]*'              '../[0-9]*'
EOF


format="\t%-19s %-22s %-27s %-8s %-8s\n"
printf "\n\n*** Testing own and python's function with arbitrary dirs\n\n"
printf "$format\n" \
    "From Directory" "To Directory" "Rel. Path" "relpath" "python"
IFS=
while read -r p; do
    eval set -- $p
    case $1 in '#'*|'') continue;; esac # Skip comments and empty lines
    # q stores quoting character, use " if ' is used in path name
    q="'"; case $1$2 in *"'"*) q='"';; esac
    rpOk=passed rp=$(relpath "$1" "$2"); test "$rp" = "$3" || rpOk=$rp
    RPOk=passed
    RP=$(python -c "import os.path; print os.path.relpath($q$2$q, $q$1$q)")
    test "$RP" = "$3" || RPOk=$RP
    printf "$format" "$q$1$q" "$q$2$q" "$q$3$q" "$q$rpOk$q" "$q$RPOk$q"
done <<-"EOF"
    # From directory    To directory           Expected relative path

    'usr/p1/..//./p4'   'p3/../p1/p6/.././/p2' '../../p1/p2'
    './home/../../work' '..//././../dir///'    '../../dir'

    'home/p1/p2'        'home/p1/p3'           '../p3'
    'home/p1/p2'        'home/p4/p5'           '../../p4/p5'
    'home/p1/p2'        'work/p6/p7'           '../../../work/p6/p7'
    'home/p1'           'work/p1/p2/p3/p4'     '../../work/p1/p2/p3/p4'
    'home'              'work/p2/p3'           '../work/p2/p3'
    '.'                 'work/p2/p3'           'work/p2/p3'
    'home/p1/p2'        'home/p1/p2/p3/p4'     'p3/p4'
    'home/p1/p2'        'home/p1/p2/p3'        'p3'
    'home/p1/p2'        'home/p1/p2'           '.'
    'home/p1/p2'        'home/p1'              '..'
    'home/p1/p2'        'home'                 '../..'
    'home/p1/p2'        '.'                    '../../..'
    'home/p1/p2'        'work'                 '../../../work'
    'home/p1/p2'        'work/p1'              '../../../work/p1'
    'home/p1/p2'        'work/p1/p2'           '../../../work/p1/p2'
    'home/p1/p2'        'work/p1/p2/p3'        '../../../work/p1/p2/p3'
    'home/p1/p2'        'work/p1/p2/p3/p4'     '../../../work/p1/p2/p3/p4'
EOF

9

여기에 많은 대답이 매일 사용하기에 실용적이지는 않습니다. 순수한 bash 에서이 작업을 올바르게 수행하는 것은 매우 어렵 기 때문에 다음과 같은 신뢰할 수있는 솔루션을 제안합니다 (의견에 묻힌 하나의 제안과 유사).

function relpath() { 
  python -c "import os,sys;print(os.path.relpath(*(sys.argv[1:])))" "$@";
}

그런 다음 현재 디렉토리를 기반으로 상대 경로를 얻을 수 있습니다.

echo $(relpath somepath)

또는 주어진 디렉토리에 상대적인 경로를 지정할 수 있습니다 :

echo $(relpath somepath /etc)  # relative to /etc

한 가지 단점은 파이썬이 필요하지만 다음과 같습니다.

  • 파이썬> = 2.6에서 동일하게 작동합니다.
  • 파일이나 디렉토리가 존재하지 않아도됩니다.
  • 파일 이름에는 더 넓은 범위의 특수 문자가 포함될 수 있습니다. 예를 들어 파일 이름에 공백이나 다른 특수 문자가 포함되어 있으면 다른 많은 솔루션이 작동하지 않습니다.
  • 스크립트를 복잡하게 만들지 않는 한 줄 함수입니다.

설치가 필요하기 때문에 반드시 포함 basename되거나 dirname더 나은 솔루션 은 아닙니다 coreutils. 누군가가 bash(심지어 호기심이 아닌) 신뢰할 수 있고 간단한 순수한 솔루션을 가지고 있다면 놀랄 것입니다.


이것은 가장 강력한 접근 방식처럼 보입니다.
dimo414

7

이 스크립트는 또는없이 .또는 절대 경로 또는 상대 경로 인 입력에 대해서만 올바른 결과를 제공합니다 ...

#!/bin/bash

# usage: relpath from to

if [[ "$1" == "$2" ]]
then
    echo "."
    exit
fi

IFS="/"

current=($1)
absolute=($2)

abssize=${#absolute[@]}
cursize=${#current[@]}

while [[ ${absolute[level]} == ${current[level]} ]]
do
    (( level++ ))
    if (( level > abssize || level > cursize ))
    then
        break
    fi
done

for ((i = level; i < cursize; i++))
do
    if ((i > level))
    then
        newpath=$newpath"/"
    fi
    newpath=$newpath".."
done

for ((i = level; i < abssize; i++))
do
    if [[ -n $newpath ]]
    then
        newpath=$newpath"/"
    fi
    newpath=$newpath${absolute[i]}
done

echo "$newpath"

1
이것은 작동하는 것 같습니다. 디렉토리가 실제로 존재하는 경우, 입력에 $ (readlink -f $ 1) 및 $ (readlink -f $ 2)를 사용하면 "."위치의 문제점을 해결할 수 있습니다. 또는 ".."가 입력에 나타납니다. 디렉토리가 실제로 존재하지 않으면 문제가 발생할 수 있습니다.
박사 사람 사람 II

7

나는이 사소한 작업에 Perl을 사용하려고합니다.

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

# Perl is magic
relative=$(perl -MFile::Spec -e 'print File::Spec->abs2rel("'$absolute'","'$current'")')

1
+1이지만 권장 : perl -MFile::Spec -e "print File::Spec->abs2rel('$absolute','$current')"절대 및 전류가 인용됩니다.
William Pursell

나는 좋아한다 relative=$(perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$absolute" "$current"). 이것은 값 자체가 펄 코드를 포함 할 수 없도록합니다!
Erik Aronesty

6

kaskuPini의 답변이 약간 개선 되어 공백으로 더 잘 재생되고 상대 경로를 통과 할 수 있습니다.

#!/bin/bash
# both $1 and $2 are paths
# returns $2 relative to $1
absolute=`readlink -f "$2"`
current=`readlink -f "$1"`
# Perl is magic
# Quoting horror.... spaces cause problems, that's why we need the extra " in here:
relative=$(perl -MFile::Spec -e "print File::Spec->abs2rel(q($absolute),q($current))")

echo $relative

4

test.sh :

#!/bin/bash                                                                 

cd /home/ubuntu
touch blah
TEST=/home/ubuntu/.//blah
echo TEST=$TEST
TMP=$(readlink -e "$TEST")
echo TMP=$TMP
REL=${TMP#$(pwd)/}
echo REL=$REL

테스트 :

$ ./test.sh 
TEST=/home/ubuntu/.//blah
TMP=/home/ubuntu/blah
REL=blah

소형화 및 강타 성 +1 당신은, 그러나, 또한 호출해야합니다 readlink$(pwd).
DevSolar

2
상대가 파일을 동일한 디렉토리에 배치해야한다는 의미는 아닙니다.
greenoldman

원래 질문에 많은 테스트 사례가 제공되지는 않지만 / home / user1에서 / home / user2 로의 상대 경로를 찾는 것과 같은 간단한 테스트에서는이 스크립트가 실패합니다 (정답 : ../user2). 이 경우 pini / jcwenger의 스크립트가 작동합니다.
michael

4

다음과 같은 맥락에서 쉽게 사용할 수있는 또 다른 솔루션 인 순수 bash+ GNU readlink:

ln -s "$(relpath "$A" "$B")" "$B"

편집 :이 경우 "$ B"가 존재하지 않거나 소프트 링크가 없는지 확인하십시오. 그렇지 않으면 relpath원하는 링크가 아닙니다.

이것은 거의 모든 현재 Linux에서 작동합니다. 경우 readlink -m귀하의 측면에서 작업을하지 않는 시도 readlink -f대신에. 가능한 업데이트 는 https://gist.github.com/hilbix/1ec361d00a8178ae8ea0 을 참조하십시오 .

: relpath A B
# Calculate relative path from A to B, returns true on success
# Example: ln -s "$(relpath "$A" "$B")" "$B"
relpath()
{
local X Y A
# We can create dangling softlinks
X="$(readlink -m -- "$1")" || return
Y="$(readlink -m -- "$2")" || return
X="${X%/}/"
A=""
while   Y="${Y%/*}"
        [ ".${X#"$Y"/}" = ".$X" ]
do
        A="../$A"
done
X="$A${X#"$Y"/}"
X="${X%/}"
echo "${X:-.}"
}

노트:

  • 파일 이름에 *또는이 포함 된 경우, 원치 않는 셸 메타 문자 확장에 대해 안전합니다 ?.
  • 출력은 다음에 대한 첫 번째 인수로 사용할 수 있습니다 ln -s.
    • relpath / /.빈 문자열이 아니라 제공
    • relpath a a디렉토리 인 a경우에도 제공a
  • 가장 일반적인 경우도 합리적인 결과를 얻기 위해 테스트되었습니다.
  • 이 솔루션은 문자열 접두사 일치를 사용하므로 readlink경로를 정규화해야합니다.
  • 덕분에 readlink -m아직 기존 경로가 작동하지 않습니다.

readlink -m사용할 수없는 이전 시스템에서는 readlink -f파일이 없으면 실패합니다. 따라서 아마도 다음과 같은 해결 방법이 필요할 것입니다.

readlink_missing()
{
readlink -m -- "$1" && return
readlink -f -- "$1" && return
[ -e . ] && echo "$(readlink_missing "$(dirname "$1")")/$(basename "$1")"
}

이것은 $1포함 .하거나 ..존재하지 않는 경로 (와 같이 /doesnotexist/./a)의 경우 실제로는 정확 하지 않지만 대부분의 경우를 다루어야합니다.

(교체 readlink -m --이상 readlink_missing).

downvote 때문에 편집

다음은이 기능이 실제로 올바른 테스트입니다.

check()
{
res="$(relpath "$2" "$1")"
[ ".$res" = ".$3" ] && return
printf ':WRONG: %-10q %-10q gives %q\nCORRECT %-10q %-10q gives %q\n' "$1" "$2" "$res" "$@"
}

#     TARGET   SOURCE         RESULT
check "/A/B/C" "/A"           ".."
check "/A/B/C" "/A.x"         "../../A.x"
check "/A/B/C" "/A/B"         "."
check "/A/B/C" "/A/B/C"       "C"
check "/A/B/C" "/A/B/C/D"     "C/D"
check "/A/B/C" "/A/B/C/D/E"   "C/D/E"
check "/A/B/C" "/A/B/D"       "D"
check "/A/B/C" "/A/B/D/E"     "D/E"
check "/A/B/C" "/A/D"         "../D"
check "/A/B/C" "/A/D/E"       "../D/E"
check "/A/B/C" "/D/E/F"       "../../D/E/F"

check "/foo/baz/moo" "/foo/bar" "../bar"

어찌할 바를 모르는? 글쎄, 이것들은 올바른 결과입니다 ! 질문에 맞지 않는다고 생각하더라도 이것이 올바른 증거입니다.

check "http://example.com/foo/baz/moo" "http://example.com/foo/bar" "../bar"

의심의 여지없이, ../bar페이지에서 bar본 페이지의 정확하고 정확한 상대 경로입니다 moo. 다른 모든 것은 잘못 될 것입니다.

명백하게 가정하는 질문, 즉 current디렉토리에 출력을 채택하는 것은 사소한 일입니다 .

absolute="/foo/bar"
current="/foo/baz/foo"
relative="../$(relpath "$absolute" "$current")"

요청한 내용을 정확하게 반환합니다.

그리고 눈썹을 높이기 전에 약간 더 복잡한 변형 relpath(작은 차이 /bash알아 냄)이 있습니다.

# Calculate relative PATH to the given DEST from the given BASE
# In the URL case, both URLs must be absolute and have the same Scheme.
# The `SCHEME:` must not be present in the FS either.
# This way this routine works for file paths an
: relpathurl DEST BASE
relpathurl()
{
local X Y A
# We can create dangling softlinks
X="$(readlink -m -- "$1")" || return
Y="$(readlink -m -- "$2")" || return
X="${X%/}/${1#"${1%/}"}"
Y="${Y%/}${2#"${2%/}"}"
A=""
while   Y="${Y%/*}"
        [ ".${X#"$Y"/}" = ".$X" ]
do
        A="../$A"
done
X="$A${X#"$Y"/}"
X="${X%/}"
echo "${X:-.}"
}

그리고 명확하게하기위한 점검 사항은 다음과 같습니다. 실제로 말한대로 작동합니다.

check()
{
res="$(relpathurl "$2" "$1")"
[ ".$res" = ".$3" ] && return
printf ':WRONG: %-10q %-10q gives %q\nCORRECT %-10q %-10q gives %q\n' "$1" "$2" "$res" "$@"
}

#     TARGET   SOURCE         RESULT
check "/A/B/C" "/A"           ".."
check "/A/B/C" "/A.x"         "../../A.x"
check "/A/B/C" "/A/B"         "."
check "/A/B/C" "/A/B/C"       "C"
check "/A/B/C" "/A/B/C/D"     "C/D"
check "/A/B/C" "/A/B/C/D/E"   "C/D/E"
check "/A/B/C" "/A/B/D"       "D"
check "/A/B/C" "/A/B/D/E"     "D/E"
check "/A/B/C" "/A/D"         "../D"
check "/A/B/C" "/A/D/E"       "../D/E"
check "/A/B/C" "/D/E/F"       "../../D/E/F"

check "/foo/baz/moo" "/foo/bar" "../bar"
check "http://example.com/foo/baz/moo" "http://example.com/foo/bar" "../bar"

check "http://example.com/foo/baz/moo/" "http://example.com/foo/bar" "../../bar"
check "http://example.com/foo/baz/moo"  "http://example.com/foo/bar/" "../bar/"
check "http://example.com/foo/baz/moo/"  "http://example.com/foo/bar/" "../../bar/"

그리고 이것이 질문에서 원하는 결과를 얻는 데 어떻게 사용될 수 있습니까?

absolute="/foo/bar"
current="/foo/baz/foo"
relative="$(relpathurl "$absolute" "$current/")"
echo "$relative"

작동하지 않는 것을 찾으면 아래 의견에 알려주십시오. 감사.

추신:

relpath여기에있는 다른 모든 대답과 대조적으로 "역전 된" 이라는 주장이있는 이유는 무엇 입니까?

변경하면

Y="$(readlink -m -- "$2")" || return

Y="$(readlink -m -- "${2:-"$PWD"}")" || return

그런 다음 BASE가 현재 디렉토리 / URL / 무엇이든지 두 번째 매개 변수를 남겨 둘 수 있습니다. 그것은 평소와 같이 유닉스 원칙입니다.

마음에 들지 않으면 Windows로 돌아갑니다. 감사.


3

슬프게도 Mark Rushakoff의 답변 (현재 삭제되었습니다- 여기 에서 코드를 참조했습니다 )은 다음에 적응했을 때 올바르게 작동하지 않는 것 같습니다.

source=/home/part2/part3/part4
target=/work/proj1/proj2

해설에 요약 된 생각은 대부분의 경우 올바르게 작동하도록 다듬을 수 있습니다. 스크립트가 소스 인수 (현재 위치)와 대상 인수 (가려는 위치)를 취하고 둘 다 절대 경로 이름이거나 둘 다 상대적이라고 가정합니다. 하나가 절대적이고 다른 것이 상대라면 가장 쉬운 것은 상대 이름 앞에 현재 작업 디렉토리를 붙이는 것입니다. 그러나 아래 코드는 그렇게하지 않습니다.


조심

아래 코드는 올바르게 작동하지만 거의 맞지 않습니다.

  1. Dennis Williamson의 의견에 언급 된 문제가 있습니다.
  2. 경로 이름을 순전히 텍스트로 처리하면 이상한 심볼릭 링크가 심각하게 망칠 수 있다는 문제도 있습니다.
  3. 이 코드는 ' xyz/./pqr' 와 같은 경로에서 길 잃은 '점'을 처리하지 않습니다 .
  4. 이 코드는 ' xyz/../pqr' 와 같은 경로에서 길잃은 '더블 도트'를 처리하지 않습니다 .
  5. 분명히 : 코드는 ./경로에서 선행 ' '을 제거하지 않습니다 .

Dennis의 코드는 1과 5를 수정하기 때문에 더 낫지 만 2, 3, 4와 같은 문제가 있습니다. Dennis의 코드를 사용하여 미리 투표하십시오.

(NB : POSIX는 realpath()경로 이름을 확인하여 심볼릭 링크가 남아 있지 않은 시스템 호출 을 제공 합니다. 입력 이름에 적용한 다음 Dennis의 코드를 사용하면 매번 올바른 답을 얻을 수 있습니다. C 코드를 작성하는 것은 쉽지 않습니다. 랩 realpath()-나는 그것을했다-하지만 그렇게하는 표준 유틸리티를 모른다.)


이를 위해 bash는 쉘보다 사용하기가 쉽지만 bash는 배열을 적절하게 지원하고 독자도 연습 할 수 있습니다. 따라서 두 개의 호환 가능한 이름이 주어지면 각각을 구성 요소로 나누십시오.

  • 상대 경로를 비워 두십시오.
  • 구성 요소는 동일하지만 다음으로 건너 뜁니다.
  • 해당 구성 요소가 다르거 나 한 경로에 대한 구성 요소가 더 이상없는 경우 :
  • 남아있는 소스 구성 요소가없고 상대 경로가 비어 있으면 "."를 추가하십시오. 처음에.
  • 나머지 소스 구성 요소마다 상대 경로 앞에 "../"를 붙입니다.
  • 남아있는 대상 구성 요소가없고 상대 경로가 비어 있으면 "."를 추가하십시오. 처음에.
  • 나머지 대상 구성 요소마다 슬래시 후 경로 끝에 구성 요소를 추가하십시오.

그러므로:

#!/bin/perl -w

use strict;

# Should fettle the arguments if one is absolute and one relative:
# Oops - missing functionality!

# Split!
my(@source) = split '/', $ARGV[0];
my(@target) = split '/', $ARGV[1];

my $count = scalar(@source);
   $count = scalar(@target) if (scalar(@target) < $count);
my $relpath = "";

my $i;
for ($i = 0; $i < $count; $i++)
{
    last if $source[$i] ne $target[$i];
}

$relpath = "." if ($i >= scalar(@source) && $relpath eq "");
for (my $s = $i; $s < scalar(@source); $s++)
{
    $relpath = "../$relpath";
}
$relpath = "." if ($i >= scalar(@target) && $relpath eq "");
for (my $t = $i; $t < scalar(@target); $t++)
{
    $relpath .= "/$target[$t]";
}

# Clean up result (remove double slash, trailing slash, trailing slash-dot).
$relpath =~ s%//%/%;
$relpath =~ s%/$%%;
$relpath =~ s%/\.$%%;

print "source  = $ARGV[0]\n";
print "target  = $ARGV[1]\n";
print "relpath = $relpath\n";

테스트 스크립트 (대괄호에는 공백과 탭이 포함됨) :

sed 's/#.*//;/^[    ]*$/d' <<! |

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

/home/part1/part2 /home/part1/part2/part3/part4
/home/part1/part2 /home/part1/part2/part3
/home/part1/part2 /home/part1/part2
/home/part1/part2 /home/part1
/home/part1/part2 /home
/home/part1/part2 /

/home/part1/part2 /work
/home/part1/part2 /work/part1
/home/part1/part2 /work/part1/part2
/home/part1/part2 /work/part1/part2/part3
/home/part1/part2 /work/part1/part2/part3/part4

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

home/part1/part2 home/part1/part2/part3/part4
home/part1/part2 home/part1/part2/part3
home/part1/part2 home/part1/part2
home/part1/part2 home/part1
home/part1/part2 home
home/part1/part2 .

home/part1/part2 work
home/part1/part2 work/part1
home/part1/part2 work/part1/part2
home/part1/part2 work/part1/part2/part3
home/part1/part2 work/part1/part2/part3/part4

!

while read source target
do
    perl relpath.pl $source $target
    echo
done

테스트 스크립트의 출력 :

source  = /home/part1/part2
target  = /home/part1/part3
relpath = ../part3

source  = /home/part1/part2
target  = /home/part4/part5
relpath = ../../part4/part5

source  = /home/part1/part2
target  = /work/part6/part7
relpath = ../../../work/part6/part7

source  = /home/part1
target  = /work/part1/part2/part3/part4
relpath = ../../work/part1/part2/part3/part4

source  = /home
target  = /work/part2/part3
relpath = ../work/part2/part3

source  = /
target  = /work/part2/part3/part4
relpath = ./work/part2/part3/part4

source  = /home/part1/part2
target  = /home/part1/part2/part3/part4
relpath = ./part3/part4

source  = /home/part1/part2
target  = /home/part1/part2/part3
relpath = ./part3

source  = /home/part1/part2
target  = /home/part1/part2
relpath = .

source  = /home/part1/part2
target  = /home/part1
relpath = ..

source  = /home/part1/part2
target  = /home
relpath = ../..

source  = /home/part1/part2
target  = /
relpath = ../../../..

source  = /home/part1/part2
target  = /work
relpath = ../../../work

source  = /home/part1/part2
target  = /work/part1
relpath = ../../../work/part1

source  = /home/part1/part2
target  = /work/part1/part2
relpath = ../../../work/part1/part2

source  = /home/part1/part2
target  = /work/part1/part2/part3
relpath = ../../../work/part1/part2/part3

source  = /home/part1/part2
target  = /work/part1/part2/part3/part4
relpath = ../../../work/part1/part2/part3/part4

source  = home/part1/part2
target  = home/part1/part3
relpath = ../part3

source  = home/part1/part2
target  = home/part4/part5
relpath = ../../part4/part5

source  = home/part1/part2
target  = work/part6/part7
relpath = ../../../work/part6/part7

source  = home/part1
target  = work/part1/part2/part3/part4
relpath = ../../work/part1/part2/part3/part4

source  = home
target  = work/part2/part3
relpath = ../work/part2/part3

source  = .
target  = work/part2/part3
relpath = ../work/part2/part3

source  = home/part1/part2
target  = home/part1/part2/part3/part4
relpath = ./part3/part4

source  = home/part1/part2
target  = home/part1/part2/part3
relpath = ./part3

source  = home/part1/part2
target  = home/part1/part2
relpath = .

source  = home/part1/part2
target  = home/part1
relpath = ..

source  = home/part1/part2
target  = home
relpath = ../..

source  = home/part1/part2
target  = .
relpath = ../../..

source  = home/part1/part2
target  = work
relpath = ../../../work

source  = home/part1/part2
target  = work/part1
relpath = ../../../work/part1

source  = home/part1/part2
target  = work/part1/part2
relpath = ../../../work/part1/part2

source  = home/part1/part2
target  = work/part1/part2/part3
relpath = ../../../work/part1/part2/part3

source  = home/part1/part2
target  = work/part1/part2/part3/part4
relpath = ../../../work/part1/part2/part3/part4

이 Perl 스크립트는 이상한 입력에도 불구하고 Unix에서 상당히 철저하게 작동합니다 (Windows 경로 이름의 모든 복잡성을 고려하지는 않음). 모듈 Cwd과 그 기능 realpath을 사용하여 존재하는 실제 이름 경로를 확인하고 존재하지 않는 경로에 대한 텍스트 분석을 수행합니다. 하나를 제외한 모든 경우에 Dennis의 스크립트와 동일한 출력을 생성합니다. 이례적인 경우는 다음과 같습니다.

source   = home/part1/part2
target   = .
relpath1 = ../../..
relpath2 = ../../../.

두 결과는 동일하지만 동일하지는 않습니다. (출력은 약간 수정 된 테스트 스크립트 버전에서 나온 것입니다. 아래 Perl 스크립트는 위의 스크립트에서와 같이 입력과 응답이 아닌 단순히 답변을 인쇄합니다.) 이제 작동하지 않는 답변을 제거해야합니까? 아마도...

#!/bin/perl -w
# Based loosely on code from: http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-10/1256.html
# Via: http://stackoverflow.com/questions/2564634

use strict;

die "Usage: $0 from to\n" if scalar @ARGV != 2;

use Cwd qw(realpath getcwd);

my $pwd;
my $verbose = 0;

# Fettle filename so it is absolute.
# Deals with '//', '/./' and '/../' notations, plus symlinks.
# The realpath() function does the hard work if the path exists.
# For non-existent paths, the code does a purely textual hack.
sub resolve
{
    my($name) = @_;
    my($path) = realpath($name);
    if (!defined $path)
    {
        # Path does not exist - do the best we can with lexical analysis
        # Assume Unix - not dealing with Windows.
        $path = $name;
        if ($name !~ m%^/%)
        {
            $pwd = getcwd if !defined $pwd;
            $path = "$pwd/$path";
        }
        $path =~ s%//+%/%g;     # Not UNC paths.
        $path =~ s%/$%%;        # No trailing /
        $path =~ s%/\./%/%g;    # No embedded /./
        # Try to eliminate /../abc/
        $path =~ s%/\.\./(?:[^/]+)(/|$)%$1%g;
        $path =~ s%/\.$%%;      # No trailing /.
        $path =~ s%^\./%%;      # No leading ./
        # What happens with . and / as inputs?
    }
    return($path);
}

sub print_result
{
    my($source, $target, $relpath) = @_;
    if ($verbose)
    {
        print "source  = $ARGV[0]\n";
        print "target  = $ARGV[1]\n";
        print "relpath = $relpath\n";
    }
    else
    {
        print "$relpath\n";
    }
    exit 0;
}

my($source) = resolve($ARGV[0]);
my($target) = resolve($ARGV[1]);
print_result($source, $target, ".") if ($source eq $target);

# Split!
my(@source) = split '/', $source;
my(@target) = split '/', $target;

my $count = scalar(@source);
   $count = scalar(@target) if (scalar(@target) < $count);
my $relpath = "";
my $i;

# Both paths are absolute; Perl splits an empty field 0.
for ($i = 1; $i < $count; $i++)
{
    last if $source[$i] ne $target[$i];
}

for (my $s = $i; $s < scalar(@source); $s++)
{
    $relpath = "$relpath/" if ($s > $i);
    $relpath = "$relpath..";
}
for (my $t = $i; $t < scalar(@target); $t++)
{
    $relpath = "$relpath/" if ($relpath ne "");
    $relpath = "$relpath$target[$t]";
}

print_result($source, $target, $relpath);

귀하 /home/part1/part2에는 /너무 많은 일이있다 ../. 그렇지 않으면 내 스크립트 .는 대상이있는 곳의 끝에 불필요 를 추가하고 올라가지 않고 내림차순으로 시작하는 것을 시작 .하지 않는 것을 제외하고는 출력과 일치합니다 ./.
추후 공지가있을 때까지 일시 중지되었습니다.

@Dennis : 나는 결과를 가로 지르는 데 시간을 보냈습니다. 때로는 그 문제를 볼 수 있었고 때로는 다시는 찾을 수 없었습니다. 선행 './'제거는 또 다른 사소한 단계입니다. '포함되지 않음'에 대한 의견. 또는 .. '도 관련이 있습니다. 실제로 작업을 올바르게 수행하는 것은 놀랍게도 어렵습니다. 이름 중 하나라도 실제로 심볼릭 링크 인 경우에는 이중입니다. 우리는 순전히 텍스트 분석을하고 있습니다.
Jonathan Leffler

@Dennis : 물론, Newcastle Connection 네트워킹이 없다면 루트를 뛰어 넘는 것은 쓸모가 없으므로 ../../../ ..와 ../../ ..는 동등합니다. 그러나 그것은 순수한 도피입니다. 당신의 비판은 정확합니다. (Newcastle Connection을 사용하면 /../host/path/on/remote/machine 표기법을 구성하고 사용하여 다른 호스트에 접근 할 수 있습니다-깔끔한 구성표. /../../network/host/ path / on / remote / network / and / host도 마찬가지입니다. Wikipedia에 있습니다.)
Jonathan Leffler

대신 UNC의 이중 슬래시가 생겼습니다.
추후 공지가있을 때까지 일시 중지되었습니다.

1
"-f"옵션을 전달하면 "readlink"유틸리티 (적어도 GNU 버전)는 realpath ()와 동등한 기능을 수행 할 수 있습니다. 예를 들어, 내 시스템에 readlink /usr/bin/vi제공 /etc/alternatives/vi하지만, 다른 심볼릭 링크입니다 그 - 반면에 readlink -f /usr/bin/vi제공 /usr/bin/vim.basic하는 모든 심볼릭 링크의 최종 목적지 인 ...
psmears

3

나는 "휴대용"쉘 코드로 이것을 작성하기위한 도전으로 당신의 질문을했습니다.

  • POSIX 쉘을 염두에두고
  • 배열과 같은 bashisms 없음
  • 전염병과 같은 외부인을 부르지 마십시오. 스크립트에는 단일 포크가 없습니다! 이는 특히 cygwin과 같이 포크 오버 헤드가 큰 시스템에서 엄청나게 빠릅니다.
  • 경로 이름 (*,?, [,])에서 glob 문자를 처리해야합니다.

POSIX 호환 쉘 (zsh, bash, ksh, ash, busybox 등)에서 실행됩니다. 작동을 확인하기위한 테스트 슈트도 포함되어 있습니다. 경로 이름의 정규화는 연습으로 남습니다. :-)

#!/bin/sh

# Find common parent directory path for a pair of paths.
# Call with two pathnames as args, e.g.
# commondirpart foo/bar foo/baz/bat -> result="foo/"
# The result is either empty or ends with "/".
commondirpart () {
   result=""
   while test ${#1} -gt 0 -a ${#2} -gt 0; do
      if test "${1%${1#?}}" != "${2%${2#?}}"; then   # First characters the same?
         break                                       # No, we're done comparing.
      fi
      result="$result${1%${1#?}}"                    # Yes, append to result.
      set -- "${1#?}" "${2#?}"                       # Chop first char off both strings.
   done
   case "$result" in
   (""|*/) ;;
   (*)     result="${result%/*}/";;
   esac
}

# Turn foo/bar/baz into ../../..
#
dir2dotdot () {
   OLDIFS="$IFS" IFS="/" result=""
   for dir in $1; do
      result="$result../"
   done
   result="${result%/}"
   IFS="$OLDIFS"
}

# Call with FROM TO args.
relativepath () {
   case "$1" in
   (*//*|*/./*|*/../*|*?/|*/.|*/..)
      printf '%s\n' "'$1' not canonical"; exit 1;;
   (/*)
      from="${1#?}";;
   (*)
      printf '%s\n' "'$1' not absolute"; exit 1;;
   esac
   case "$2" in
   (*//*|*/./*|*/../*|*?/|*/.|*/..)
      printf '%s\n' "'$2' not canonical"; exit 1;;
   (/*)
      to="${2#?}";;
   (*)
      printf '%s\n' "'$2' not absolute"; exit 1;;
   esac

   case "$to" in
   ("$from")   # Identical directories.
      result=".";;
   ("$from"/*) # From /x to /x/foo/bar -> foo/bar
      result="${to##$from/}";;
   ("")        # From /foo/bar to / -> ../..
      dir2dotdot "$from";;
   (*)
      case "$from" in
      ("$to"/*)       # From /x/foo/bar to /x -> ../..
         dir2dotdot "${from##$to/}";;
      (*)             # Everything else.
         commondirpart "$from" "$to"
         common="$result"
         dir2dotdot "${from#$common}"
         result="$result/${to#$common}"
      esac
      ;;
   esac
}

set -f # noglob

set -x
cat <<EOF |
/ / .
/- /- .
/? /? .
/?? /?? .
/??? /??? .
/?* /?* .
/* /* .
/* /** ../**
/* /*** ../***
/*.* /*.** ../*.**
/*.??? /*.?? ../*.??
/[] /[] .
/[a-z]* /[0-9]* ../[0-9]*
/foo /foo .
/foo / ..
/foo/bar / ../..
/foo/bar /foo ..
/foo/bar /foo/baz ../baz
/foo/bar /bar/foo  ../../bar/foo
/foo/bar/baz /gnarf/blurfl/blubb ../../../gnarf/blurfl/blubb
/foo/bar/baz /gnarf ../../../gnarf
/foo/bar/baz /foo/baz ../../baz
/foo. /bar. ../bar.
EOF
while read FROM TO VIA; do
   relativepath "$FROM" "$TO"
   printf '%s\n' "FROM: $FROM" "TO:   $TO" "VIA:  $result"
   if test "$result" != "$VIA"; then
      printf '%s\n' "OOOPS! Expected '$VIA' but got '$result'"
   fi
done

# vi: set tabstop=3 shiftwidth=3 expandtab fileformat=unix :

2

내 솔루션 :

computeRelativePath() 
{

    Source=$(readlink -f ${1})
    Target=$(readlink -f ${2})

    local OLDIFS=$IFS
    IFS="/"

    local SourceDirectoryArray=($Source)
    local TargetDirectoryArray=($Target)

    local SourceArrayLength=$(echo ${SourceDirectoryArray[@]} | wc -w)
    local TargetArrayLength=$(echo ${TargetDirectoryArray[@]} | wc -w)

    local Length
    test $SourceArrayLength -gt $TargetArrayLength && Length=$SourceArrayLength || Length=$TargetArrayLength


    local Result=""
    local AppendToEnd=""

    IFS=$OLDIFS

    local i

    for ((i = 0; i <= $Length + 1 ; i++ ))
    do
            if [ "${SourceDirectoryArray[$i]}" = "${TargetDirectoryArray[$i]}" ]
            then
                continue    
            elif [ "${SourceDirectoryArray[$i]}" != "" ] && [ "${TargetDirectoryArray[$i]}" != "" ] 
            then
                AppendToEnd="${AppendToEnd}${TargetDirectoryArray[${i}]}/"
                Result="${Result}../"               

            elif [ "${SourceDirectoryArray[$i]}" = "" ]
            then
                Result="${Result}${TargetDirectoryArray[${i}]}/"
            else
                Result="${Result}../"
            fi
    done

    Result="${Result}${AppendToEnd}"

    echo $Result

}

이것은 매우 휴대용입니다 :)
Anonymous

2

여기 내 버전이 있습니다. @Offirmo답변 을 기반으로합니다 . 대시 호환으로 만들고 다음 테스트 케이스 실패를 수정했습니다.

./compute-relative.sh "/a/b/c/de/f/g" "/a/b/c/def/g/" -> "../..f/g/"

지금:

CT_FindRelativePath "/a/b/c/de/f/g" "/a/b/c/def/g/" -> "../../../def/g/"

코드를 참조하십시오 :

# both $1 and $2 are absolute paths beginning with /
# returns relative path to $2/$target from $1/$source
CT_FindRelativePath()
{
    local insource=$1
    local intarget=$2

    # Ensure both source and target end with /
    # This simplifies the inner loop.
    #echo "insource : \"$insource\""
    #echo "intarget : \"$intarget\""
    case "$insource" in
        */) ;;
        *) source="$insource"/ ;;
    esac

    case "$intarget" in
        */) ;;
        *) target="$intarget"/ ;;
    esac

    #echo "source : \"$source\""
    #echo "target : \"$target\""

    local common_part=$source # for now

    local result=""

    #echo "common_part is now : \"$common_part\""
    #echo "result is now      : \"$result\""
    #echo "target#common_part : \"${target#$common_part}\""
    while [ "${target#$common_part}" = "${target}" -a "${common_part}" != "//" ]; 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
        if [ -z "${result}" ]; then
            result="../"
        else
            result="../$result"
        fi
        #echo "(w) common_part is now : \"$common_part\""
        #echo "(w) result is now      : \"$result\""
        #echo "(w) target#common_part : \"${target#$common_part}\""
    done

    #echo "(f) common_part is     : \"$common_part\""

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

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

    if [ -n "${result}" -a -n "${forward_part}" ]; then
        #echo "(simple concat)"
        result="$result$forward_part"
    elif [ -n "${forward_part}" ]; then
        result="$forward_part"
    fi
    #echo "result = \"$result\""

    # if a / was added to target and result ends in / then remove it now.
    if [ "$intarget" != "$target" ]; then
        case "$result" in
            */) result=$(echo "$result" | awk '{ string=substr($0, 1, length($0)-1); print string; }' ) ;;
        esac
    fi

    echo $result

    return 0
}

1

이것도 트릭을 할 것 같아요 ... (내장 테스트가 제공됩니다) :)

좋아, 약간의 오버 헤드가 예상되었지만 우리는 Bourne 쉘을하고 있습니다! ;)

#!/bin/sh

#
# Finding the relative path to a certain file ($2), given the absolute path ($1)
# (available here too http://pastebin.com/tWWqA8aB)
#
relpath () {
  local  FROM="$1"
  local    TO="`dirname  $2`"
  local  FILE="`basename $2`"
  local  DEBUG="$3"

  local FROMREL=""
  local FROMUP="$FROM"
  while [ "$FROMUP" != "/" ]; do
    local TOUP="$TO"
    local TOREL=""
    while [ "$TOUP" != "/" ]; do
      [ -z "$DEBUG" ] || echo 1>&2 "$DEBUG$FROMUP =?= $TOUP"
      if [ "$FROMUP" = "$TOUP" ]; then
        echo "${FROMREL:-.}/$TOREL${TOREL:+/}$FILE"
        return 0
      fi
      TOREL="`basename $TOUP`${TOREL:+/}$TOREL"
      TOUP="`dirname $TOUP`"
    done
    FROMREL="..${FROMREL:+/}$FROMREL"
    FROMUP="`dirname $FROMUP`"
  done
  echo "${FROMREL:-.}${TOREL:+/}$TOREL/$FILE"
  return 0
}

relpathshow () {
  echo " - target $2"
  echo "   from   $1"
  echo "   ------"
  echo "   => `relpath $1 $2 '      '`"
  echo ""
}

# If given 2 arguments, do as said...
if [ -n "$2" ]; then
  relpath $1 $2

# If only one given, then assume current directory
elif [ -n "$1" ]; then
  relpath `pwd` $1

# Otherwise perform a set of built-in tests to confirm the validity of the method! ;)
else

  relpathshow /usr/share/emacs22/site-lisp/emacs-goodies-el \
              /usr/share/emacs22/site-lisp/emacs-goodies-el/filladapt.el

  relpathshow /usr/share/emacs23/site-lisp/emacs-goodies-el \
              /usr/share/emacs22/site-lisp/emacs-goodies-el/filladapt.el

  relpathshow /usr/bin \
              /usr/share/emacs22/site-lisp/emacs-goodies-el/filladapt.el

  relpathshow /usr/bin \
              /usr/share/emacs22/site-lisp/emacs-goodies-el/filladapt.el

  relpathshow /usr/bin/share/emacs22/site-lisp/emacs-goodies-el \
              /etc/motd

  relpathshow / \
              /initrd.img
fi

1

이 스크립트는 경로 이름에서만 작동합니다. 파일이 없어도됩니다. 전달 된 경로가 절대적이지 않은 경우 동작이 약간 이례적이지만 두 경로가 모두 상대 인 경우 예상대로 작동해야합니다.

OS X에서만 테스트했기 때문에 이식성이 떨어질 수 있습니다.

#!/bin/bash
set -e
declare SCRIPT_NAME="$(basename $0)"
function usage {
    echo "Usage: $SCRIPT_NAME <base path> <target file>"
    echo "       Outputs <target file> relative to <base path>"
    exit 1
}

if [ $# -lt 2 ]; then usage; fi

declare base=$1
declare target=$2
declare -a base_part=()
declare -a target_part=()

#Split path elements & canonicalize
OFS="$IFS"; IFS='/'
bpl=0;
for bp in $base; do
    case "$bp" in
        ".");;
        "..") let "bpl=$bpl-1" ;;
        *) base_part[${bpl}]="$bp" ; let "bpl=$bpl+1";;
    esac
done
tpl=0;
for tp in $target; do
    case "$tp" in
        ".");;
        "..") let "tpl=$tpl-1" ;;
        *) target_part[${tpl}]="$tp" ; let "tpl=$tpl+1";;
    esac
done
IFS="$OFS"

#Count common prefix
common=0
for (( i=0 ; i<$bpl ; i++ )); do
    if [ "${base_part[$i]}" = "${target_part[$common]}" ] ; then
        let "common=$common+1"
    else
        break
    fi
done

#Compute number of directories up
let "updir=$bpl-$common" || updir=0 #if the expression is zero, 'let' fails

#trivial case (after canonical decomposition)
if [ $updir -eq 0 ]; then
    echo .
    exit
fi

#Print updirs
for (( i=0 ; i<$updir ; i++ )); do
    echo -n ../
done

#Print remaining path
for (( i=$common ; i<$tpl ; i++ )); do
    if [ $i -ne $common ]; then
        echo -n "/"
    fi
    if [ "" != "${target_part[$i]}" ] ; then
        echo -n "${target_part[$i]}"
    fi
done
#One last newline
echo

또한 코드는 약간 복사 및 붙여 넣기가 쉽지만 다소 빨리 필요했습니다.
juancn

좋아 ... 그냥 내가 필요한 것. 그리고 당신은 내가 본 다른 대부분 (일반적으로 정규식 대체에 의존하는 것)보다 나은 정규화 루틴을 포함 시켰습니다.
drwatsoncode

0

이 답변은 질문의 Bash 부분을 다루지는 않지만 Emacs 에서이 기능을 구현하기 위해이 질문에 대한 답변을 사용하려고했기 때문에 그것을 버릴 것입니다.

이맥스는 실제로 다음과 같은 기능을 가지고 있습니다.

ELISP> (file-relative-name "/a/b/c" "/a/b/c")
"."
ELISP> (file-relative-name "/a/b/c" "/a/b")
"c"
ELISP> (file-relative-name "/a/b/c" "/c/b")
"../../a/b/c"

필자가 최근에 추가 한 파이썬 답변 ( relpath기능) file-relative-name이 제공 한 테스트 사례와 동일하게 작동 한다고 생각합니다 .
게리 Wisniewski

-1

다른 프로그램을 호출하지 않고 수행하는 쉘 스크립트는 다음과 같습니다.

#! /bin/env bash 

#bash script to find the relative path between two directories

mydir=${0%/}
mydir=${0%/*}
creadlink="$mydir/creadlink"

shopt -s extglob

relpath_ () {
        path1=$("$creadlink" "$1")
        path2=$("$creadlink" "$2")
        orig1=$path1
        path1=${path1%/}/
        path2=${path2%/}/

        while :; do
                if test ! "$path1"; then
                        break
                fi
                part1=${path2#$path1}
                if test "${part1#/}" = "$part1"; then
                        path1=${path1%/*}
                        continue
                fi
                if test "${path2#$path1}" = "$path2"; then
                        path1=${path1%/*}
                        continue
                fi
                break
        done
        part1=$path1
        path1=${orig1#$part1}
        depth=${path1//+([^\/])/..}
        path1=${path2#$path1}
        path1=${depth}${path2#$part1}
        path1=${path1##+(\/)}
        path1=${path1%/}
        if test ! "$path1"; then
                path1=.
        fi
        printf "$path1"

}

relpath_test () {
        res=$(relpath_ /path1/to/dir1 /path1/to/dir2 )
        expected='../dir2'
        test_results "$res" "$expected"

        res=$(relpath_ / /path1/to/dir2 )
        expected='path1/to/dir2'
        test_results "$res" "$expected"

        res=$(relpath_ /path1/to/dir2 / )
        expected='../../..'
        test_results "$res" "$expected"

        res=$(relpath_ / / )
        expected='.'
        test_results "$res" "$expected"

        res=$(relpath_ /path/to/dir2/dir3 /path/to/dir1/dir4/dir4a )
        expected='../../dir1/dir4/dir4a'
        test_results "$res" "$expected"

        res=$(relpath_ /path/to/dir1/dir4/dir4a /path/to/dir2/dir3 )
        expected='../../../dir2/dir3'
        test_results "$res" "$expected"

        #res=$(relpath_ . /path/to/dir2/dir3 )
        #expected='../../../dir2/dir3'
        #test_results "$res" "$expected"
}

test_results () {
        if test ! "$1" = "$2"; then
                printf 'failed!\nresult:\nX%sX\nexpected:\nX%sX\n\n' "$@"
        fi
}

#relpath_test

출처 : http://www.ynform.org/w/Pub/Relpath


1
이것은 POSIX가 아닌 $ {param / pattern / subst} 구문을 사용하기 때문에 실제로 이식성이 없습니다 (2011 년 기준).
Jens

참조 된 소스 ynform.org/w/Pub/Relpath 는 스크립트 내용이 여러 번 포함 된 완전히 왜곡 된 Wiki 페이지를 가리키며 , 비디오 라인과 섞여 있으며, 찾을 수없는 명령에 대한 오류 메시지와 기타 정보가 없습니다. 원본을 연구하는 사람에게는 전혀 쓸모가 없습니다.
Jens

-1

나는 이와 같은 것이 필요했지만 상징적 인 링크도 해결했습니다. pwd에 해당 용도로 -P 플래그가 있음을 발견했습니다. 내 스크립트 조각이 추가되었습니다. 쉘 스크립트의 함수 내에 있으므로 $ 1과 $ 2입니다. START_ABS에서 END_ABS까지의 상대 경로 인 결과 값은 UPDIRS 변수에 있습니다. 스크립트 cd는 pwd -P를 실행하기 위해 각 매개 변수 디렉토리에 있으며 이는 상대 경로 매개 변수가 처리됨을 의미합니다. 건배, 짐

SAVE_DIR="$PWD"
cd "$1"
START_ABS=`pwd -P`
cd "$SAVE_DIR"
cd "$2"
END_ABS=`pwd -P`

START_WORK="$START_ABS"
UPDIRS=""

while test -n "${START_WORK}" -a "${END_ABS/#${START_WORK}}" '==' "$END_ABS";
do
    START_WORK=`dirname "$START_WORK"`"/"
    UPDIRS=${UPDIRS}"../"
done
UPDIRS="$UPDIRS${END_ABS/#${START_WORK}}"
cd "$SAVE_DIR"
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.