두 경로 사이의 상대 링크 얻기


21

말 나는 두 개의 경로를 가지고 <source_path><target_path>. 나는 표현하는 방법이 있는지 자동으로 찾아 내 쉘 (zsh을)를하고자 <target_path>에서 <source_path>상대 경로로가.

예를 들어 봅시다

  • <source_path> 이다 /foo/bar/something
  • <target_path> 이다 /foo/hello/world

결과는 ../../hello/world

왜 이것이 필요한지 :

가능한 경우 항상 상대 심볼릭 링크 <source_path><target_path>사용하여 심볼릭 링크를 만들려면 심볼릭 링크를 만들어야합니다. 그렇지 않으면 Samba 서버가 Windows의 네트워크에서 이러한 파일에 액세스 할 때 파일을 올바르게 표시하지 않기 때문에 (sys 관리자가 아니며 이 설정을 제어 할 수 없음)

그 가정 <target_path><source_path>절대 경로이며, 다음은 심볼 링크 생성 절대 경로를 가리키는 .

ln -s <target_path> <source_path>

그래서 그것은 내 필요에 맞지 않습니다. 수백 개의 파일에 대해이 작업을 수행해야하므로 수동으로 수정할 수 없습니다.

이 문제를 해결하는 쉘 내장 기능이 있습니까?


symlinks절대 링크를 상대 링크로 변환 하는 데 사용할 수 있습니다 .
Stéphane Chazelas

@StephaneChazelas 어떻게? 설명하는 답변을 게시 할 수 있습니까?
terdon

답변:


10

symlinks명령을 사용하여 절대 경로를 상대 경로로 변환 할 수 있습니다 .

/tmp$ mkdir -p 1/{a,b,c} 2
/tmp$ cd 2
/tmp/2$ ln -s /tmp/1/* .
/tmp/2$ ls -l
total 0
lrwxrwxrwx 1 stephane stephane 8 Jul 31 16:32 a -> /tmp/1/a/
lrwxrwxrwx 1 stephane stephane 8 Jul 31 16:32 b -> /tmp/1/b/
lrwxrwxrwx 1 stephane stephane 8 Jul 31 16:32 c -> /tmp/1/c/

우리는 절대 링크를 가지고 있습니다. 상대 링크로 변환합시다 :

/tmp/2$ symlinks -cr .
absolute: /tmp/2/a -> /tmp/1/a
changed:  /tmp/2/a -> ../1/a
absolute: /tmp/2/b -> /tmp/1/b
changed:  /tmp/2/b -> ../1/b
absolute: /tmp/2/c -> /tmp/1/c
changed:  /tmp/2/c -> ../1/c
/tmp/2$ ls -l
total 0
lrwxrwxrwx 1 stephane stephane 6 Jul 31 16:32 a -> ../1/a/
lrwxrwxrwx 1 stephane stephane 6 Jul 31 16:32 b -> ../1/b/
lrwxrwxrwx 1 stephane stephane 6 Jul 31 16:32 c -> ../1/c/

참고 문헌


스테판 감사합니다. 이것은 근본 문제를 해결하므로 동의했습니다. 즉, 두 경로 (소스 및 대상)를 가져 와서 소스에서 대상으로의 상대 경로를 출력하는 (예 : zsh 함수) 것이 좋습니다. 그것은 제목에서 묻는 질문을 해결할 것입니다.
Amelio Vazquez-Reina


24

다음과 같은 realpath명령 (GNU의 일부 coreutils; > = 8.23 )을 사용해보십시오 .

realpath --relative-to=/foo/bar/something /foo/hello/world

macOS를 사용하는 경우 다음을 통해 GNU 버전을 설치 brew install coreutils하고를 사용하십시오 grealpath.

명령이 성공하려면 두 경로가 모두 존재해야합니다. 이 중 하나가 존재하지 않더라도 상대 경로가 필요하면 -m 스위치를 추가하십시오.

자세한 예 는 현재 디렉토리에서 절대 경로를 상대 경로로 변환을 참조하십시오 .


나는 얻는다 realpath: unrecognized option '--relative-to=.': (realpath version 1.19
phuclv

@ LưuVĩnhPhúc GNU / Linux 버전의을 사용해야 realpath합니다. macOS를 사용하는 경우 다음을 통해 설치하십시오 brew install coreutils..
kenorb

아니오, 저는 우분투 14.04 LTS에 있습니다
phuclv

@ LưuVĩnhPhúc realpath (GNU coreutils) 8.26매개 변수가있는 곳에 있습니다. gnu.org/software/coreutils/realpath를 참조하십시오. 별칭을 사용하지 않는지 (예 :로 실행 \realpath) 및에서 버전을 설치하는 방법을 다시 확인하십시오 coreutils.
kenorb


7

필자는 @EvanTeitelman의 답변과 동일한 게시물에서 가져온이 파이썬 솔루션도 언급 할 가치가 있다고 생각합니다. 이것을 다음에 추가하십시오 ~/.zshrc:

relpath() python -c 'import os.path, sys;\
  print os.path.relpath(sys.argv[1],sys.argv[2])' "$1" "${2-$PWD}"

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

$ relpath /usr/local/share/doc/emacs /usr/local/share/fonts
../doc/emacs

@ StephaneChazelas 이것은 링크 된 답변에서 직접 복사하여 붙여 넣었습니다. 나는 Perl 남자이며 본질적으로 파이썬을 알지 못하므로 사용법을 모른다 sys.argv. 게시 된 코드가 잘못되었거나 개선 될 수 있으면 언제든지 감사하십시오.
terdon

@StephaneChazelas 편집 해 주셔서 감사합니다.
terdon

7

이를 처리하기 위해 쉘 내장이 없습니다. 그래도 Stack Overflow에서 해결책을 찾았 습니다 . 여기에 약간의 수정 사항으로 솔루션을 복사 했으며이 답변을 커뮤니티 위키로 표시했습니다.

relpath() {
    # both $1 and $2 are absolute paths beginning with /
    # $1 must be a canonical path; that is none of its directory
    # components may be ".", ".." or a symbolic link
    #
    # returns relative path to $2/$target from $1/$source
    source=$1
    target=$2

    common_part=$source
    result=

    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#?}
    fi

    printf '%s\n' "$result"
}

이 기능을 다음과 같이 사용할 수 있습니다.

source=/foo/bar/something
target=/foo/hello/world
ln -s "$(relpath "$source" "$target")" "$source"

3
# Prints out the relative path between to absolute paths. Trivial.
#
# Parameters:
# $1 = first path
# $2 = second path
#
# Output: the relative path between 1st and 2nd paths
relpath() {
    local pos="${1%%/}" ref="${2%%/}" down=''

    while :; do
        test "$pos" = '/' && break
        case "$ref" in $pos/*) break;; esac
        down="../$down"
        pos=${pos%/*}
    done

    echo "$down${ref##$pos/}"
}

이것은 문제에 대한 가장 단순하고 가장 아름다운 해결책입니다.

또한 모든 본과 같은 껍질에서 작동해야합니다.

그리고 여기에있는 지혜는 외부 유틸리티 나 복잡한 조건이 전혀 필요 없다는 것입니다. 몇 가지 접두사 / 접미사 대체와 while 루프 만 있으면 bourne 같은 쉘에서 수행되는 모든 작업을 수행 할 수 있습니다.

PasteBin을 통해서도 이용 가능 : http://pastebin.com/CtTfvime


솔루션 주셔서 감사합니다. 나는 그것을 작동 Makefile시키기 위해 줄에 큰 따옴표 쌍을 추가해야한다는 것을 알았습니다 pos=${pos%/*}(물론 줄마다 세미콜론과 백 슬래시).
Circonflexe

아이디어가 작동하지 않는 경우 현재 버전 많음에 나쁜 것이 아니라 : relpath /tmp /tmp, relpath /tmp /tmp/a, relpath /tmp/a /. 또한 모든 경로가 절대 /tmp/a/..
적이고

0

나는 이것을 확실하게 (그리고 물론 파일 존재를 확인하지 않고) bash 스크립트 를 작성해야했다.

용법

relpath <symlink path> <source path>

상대 소스 경로를 반환합니다.

예:

#/a/b/c/d/e   ->  /a/b/CC/DD/EE       ->  ../../CC/DD/EE
relpath /a/b/c/d/e   /a/b/CC/DD/EE

#./a/b/c/d/e   ->  ./a/b/CC/DD/EE       ->  ../../CC/DD/EE
relpath ./a/b/c/d/e  ./a/b/CC/DD/EE

둘 다 얻다 ../../CC/DD/EE


빠른 테스트를 위해 다음을 실행할 수 있습니다.

curl -sL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- --test

결과 : 테스트 목록

/a             ->  /                 ->  .
/a/b           ->  /                 ->  ..
/a/b           ->  /a                ->  .
/a/b/c         ->  /a                ->  ..
/a/b/c         ->  /a/b              ->  .
/a/b/c         ->  /a/b/CC           ->  CC
/a/b/c/d/e     ->  /a                ->  ../../..
/a/b/c/d/e     ->  /a/b              ->  ../..
/a/b/c/d/e     ->  /a/b/CC           ->  ../../CC
/a/b/c/d/e     ->  /a/b/CC/DD/EE     ->  ../../CC/DD/EE
./a            ->  ./                ->  .
./a/b          ->  ./                ->  ..
./a/b          ->  ./a               ->  .
./a/b/c        ->  ./a               ->  ..
./a/b/c        ->  ./a/b             ->  .
./a/b/c        ->  ./a/b/CC          ->  CC
./a/b/c/d/e    ->  ./a               ->  ../../..
./a/b/c/d/e    ->  ./a/b/CC          ->  ../../CC
./a/b/c/d/e    ->  ./a/b/CC/DD/EE    ->  ../../CC/DD/EE
/a             ->  /x                ->  x
/a/b           ->  /x                ->  ../x
/a/b/c         ->  /x                ->  ../../x
/a             ->  /x/y              ->  x/y
/a/b           ->  /x/y              ->  ../x/y
/a/b/c         ->  /x/y              ->  ../../x/y
/x             ->  /a                ->  a
/x             ->  /a/b              ->  a/b
/x             ->  /a/b/c            ->  a/b/c
/x/y           ->  /a                ->  ../a
/x/y           ->  /a/b              ->  ../a/b
/x/y           ->  /a/b/c            ->  ../a/b/c
./a a/b/c/d/e  ->  ./a a/b/CC/DD/EE  ->  ../../CC/DD/EE
/x x/y y       ->  /a a/b b/c c      ->  ../a a/b b/c c

BTW,이 스크립트를 저장하지 않고 사용 하고이 스크립트를 신뢰하는 경우 bash 트릭으로 두 가지 방법이 있습니다.

relpath다음 명령으로 bash 함수로 가져 옵니다. (bash 4 이상 필요)

source <(curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath)

또는 curl bash 콤보와 같이 스크립트를 즉석에서 호출하십시오.

curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- /a/b/c/d/e /a/b/CC/DD/EE
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.