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ą?


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


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


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 .


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

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

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


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.