Uzyskiwanie względnych połączeń między dwiema ścieżkami

21

Powiedz, że mam dwie ścieżki: <source_path>i <target_path>. Chciałbym moją skorupę (zsh), aby automatycznie sprawdzić, czy istnieje sposób, aby reprezentować <target_path>od <source_path>jako ścieżkę względną.

Np. Załóżmy

  • <source_path> jest /foo/bar/something
  • <target_path> jest /foo/hello/world

Wynik byłby ../../hello/world

Dlaczego potrzebuję tego:

Musi Lubię tworzyć dowiązania symbolicznego od <source_path>celu <target_path>za pomocą względnego łącza symboliczne ile to możliwe, ponieważ w przeciwnym razie nasz serwer Samba nie pokazuje plik poprawnie, gdy dostęp do tych plików w sieci w systemie Windows (nie jestem sys Administrator, i don” mieć kontrolę nad tym ustawieniem)

Zakładając, że <target_path>i <source_path>są ścieżki bezwzględne, co następuje tworzy dowiązanie symboliczne wskazujące na bezwzględną ścieżkę .

ln -s <target_path> <source_path>

więc to nie działa na moje potrzeby. Muszę to zrobić dla setek plików, więc nie mogę tego ręcznie naprawić.

Jakieś wbudowane powłoki, które się tym zajmują?

Amelio Vazquez-Reina
źródło
Możesz użyć symlinksdo konwersji linków bezwzględnych na względne.
Stéphane Chazelas
@StephaneChazelas jak? Czy możesz napisać odpowiedź wyjaśniającą?
terdon

Odpowiedzi:

10

Możesz użyć symlinkspolecenia, aby przekonwertować ścieżki bezwzględne na względne:

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

Mamy linki bezwzględne, przekonwertujmy je na względne:

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

Referencje

Stéphane Chazelas
źródło
Dzięki Stephane. To rozwiązuje główny problem, więc zaakceptowałem. To powiedziawszy, byłoby wspaniale mieć coś (np. Funkcję zsh), która pobierze dwie ścieżki (źródło i cel) i wyprowadza względną ścieżkę od źródła do celu (tutaj nie ma dowiązań symbolicznych). To rozwiązałoby również pytanie zadane w tytule.
Amelio Vazquez-Reina
@ user815423426, terdon odpowiedział na tę część
Stéphane Chazelas
24

Spróbuj użyć realpathpolecenia (część GNU coreutils; > = 8,23 ), np .:

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

Jeśli używasz systemu macOS, zainstaluj wersję GNU przez: brew install coreutilsi użyj grealpath.

Zauważ, że obie ścieżki muszą istnieć, aby polecenie zakończyło się powodzeniem. Jeśli i tak potrzebujesz ścieżki względnej, nawet jeśli jedna z nich nie istnieje, dodaj przełącznik -m.

Aby uzyskać więcej przykładów, zobacz Konwertuj ścieżkę bezwzględną na ścieżkę względną, biorąc pod uwagę bieżący katalog .

kenorb
źródło
Dostaję realpath: unrecognized option '--relative-to=.':( realpath wersja 1.19
phuclv
@ LưuVĩnhPhúc Musisz użyć wersji GNU / Linux realpath. Jeśli jesteś na MacOS, zainstalować poprzez: brew install coreutils.
kenorb
nie, jestem na Ubuntu 14.04 LTS
phuclv
@ LưuVĩnhPhúc Jestem realpath (GNU coreutils) 8.26tam, gdzie obecny jest parametr. Zobacz: gnu.org/software/coreutils/realpath Sprawdź dwukrotnie, czy nie używasz aliasu (np. Uruchom jako \realpath) i jak zainstalować wersję z coreutils.
kenorb
7

Myślę, że to rozwiązanie Pythona (wzięte z tego samego postu, co odpowiedź @ EvanTeitelman) również jest warte wspomnienia. Dodaj to do ~/.zshrc:

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

Następnie możesz na przykład:

$ relpath /usr/local/share/doc/emacs /usr/local/share/fonts
../doc/emacs
terdon
źródło
@StephaneChazelas była to bezpośrednia kopia wklejona z połączonej odpowiedzi. Jestem facetem Perla i zasadniczo nie znam pytona, więc nie wiem, jak go używać sys.argv. Jeśli wysłany kod jest nieprawidłowy lub można go poprawić, nie krępuj się, dziękuję.
terdon
@StephaneChazelas Rozumiem teraz, dziękuję za edycję.
terdon
7

Nie ma żadnych wbudowanych powłok, które by to załatwiły. Znalazłem jednak rozwiązanie dotyczące przepełnienia stosu . Skopiowałem tutaj rozwiązanie z niewielkimi modyfikacjami i zaznaczyłem tę odpowiedź jako wiki społeczności.

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

Możesz użyć tej funkcji w następujący sposób:

source=/foo/bar/something
target=/foo/hello/world
ln -s "$(relpath "$source" "$target")" "$source"
26112
źródło
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/}"
}

To najprostsze i najpiękniejsze rozwiązanie problemu.

Powinien również działać na wszystkich muszlach typu bourne.

Oto mądrość: absolutnie nie ma potrzeby korzystania z zewnętrznych narzędzi ani nawet skomplikowanych warunków. Kilka podstawień prefiksów / sufiksów i pętla while to wszystko, czego potrzebujesz, aby uzyskać prawie wszystko, co zrobiono na powłokach typu bourne.

Dostępne również przez PasteBin: http://pastebin.com/CtTfvime

Arto Pekkanen
źródło
Dzięki za twoje rozwiązanie. Odkryłem, że aby to działało Makefile, musiałem dodać do wiersza parę podwójnych cudzysłowów pos=${pos%/*}(i oczywiście średniki i odwrotne ukośniki na końcu każdego wiersza).
Circonflexe
Pomysł nie jest zły, ale w aktualnej wersji dużą ilością przypadków nie działają: relpath /tmp /tmp, relpath /tmp /tmp/a, relpath /tmp/a /. Na dodatek zapominasz wspomnieć, że wszystkie ścieżki muszą być absolutne ORAZ /tmp/a/..
kanonizowane
0

Musiałem napisać skrypt bash, aby zrobić to niezawodnie (i oczywiście bez sprawdzania istnienia pliku).

Stosowanie

relpath <symlink path> <source path>

Zwraca względną ścieżkę źródłową.

przykład:

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

oba dostają ../../CC/DD/EE


Aby mieć szybki test, możesz uruchomić

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

wynik: lista testów

/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, jeśli chcesz użyć tego skryptu bez zapisywania go i ufasz temu skryptowi, masz dwa sposoby na to dzięki bash trick.

Zaimportuj relpathjako funkcję bash, wykonując następujące polecenie. (Wymagaj bash 4+)

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

lub wywołać skrypt w locie, jak w przypadku kombinacji 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
osexp2003
źródło