Relative Verknüpfungen zwischen zwei Pfaden erhalten

21

Angenommen, ich habe zwei Wege: <source_path>und <target_path>. Ich würde meine Shell (zsh) wie automatisch, um herauszufinden , ob es eine Möglichkeit darzustellen , ist <target_path>aus <source_path>als relativer Pfad.

Nehmen wir zum Beispiel an

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

Das Ergebnis wäre ../../hello/world

Warum ich das brauche:

Ich möchte einen symbolischen Link von <source_path>zur <target_path>Verwendung eines Verwandten erstellen symbolischen Link , wann immer möglich, da sonst unser Samba - Server die Datei nicht richtig funktioniert zeigen , wenn ich diese Dateien auf dem Netzwerk von Windows - Zugriff (Ich bin nicht der sys admin, und don‘ keine Kontrolle über diese Einstellung haben)

Unter der Annahme, dass <target_path>und <source_path>absolute Pfade sind, wird im Folgenden eine symbolische Verknüpfung erstellt, die auf einen absoluten Pfad verweist .

ln -s <target_path> <source_path>

es funktioniert also nicht für meine bedürfnisse. Ich muss dies für Hunderte von Dateien tun, damit ich es nicht einfach manuell reparieren kann.

Gibt es Shell-Einbauten, die sich darum kümmern?

Amelio Vazquez-Reina
quelle
Mit können Sie symlinksabsolute Links in relative konvertieren.
Stéphane Chazelas
@StephaneChazelas wie? Könnten Sie eine Antwort mit Erklärungen posten?
Terdon

Antworten:

10

Sie können den symlinksBefehl verwenden, um absolute Pfade in relative Pfade umzuwandeln:

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

Wir haben absolute Links, konvertieren wir sie in relative:

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

Verweise

Stéphane Chazelas
quelle
Danke Stephane. Dies löst das Wurzelproblem, also habe ich zugestimmt. Das heißt, es wäre großartig, etwas zu haben (z. B. eine zsh-Funktion), das zwei Pfade (Quelle und Ziel) verwendet und den relativen Pfad von Quelle zu Ziel ausgibt (hier keine symbolischen Links). Damit wäre auch die im Titel gestellte Frage gelöst.
Amelio Vazquez-Reina
@ user815423426, terdon hat diesen Teil beantwortet
Stéphane Chazelas
24

Versuchen Sie es mit einem realpathBefehl (Teil von GNU coreutils; > = 8.23 ), zB:

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

Wenn Sie MacOS verwenden, installieren Sie die GNU-Version über: brew install coreutilsund verwenden Sie grealpath.

Beachten Sie, dass beide Pfade vorhanden sein müssen, damit der Befehl erfolgreich ausgeführt werden kann. Wenn Sie den relativen Pfad trotzdem benötigen, auch wenn einer nicht vorhanden ist, fügen Sie den Schalter -m hinzu.

Weitere Beispiele finden Sie unter Konvertieren des absoluten Pfads in einen relativen Pfad bei einem gegebenen aktuellen Verzeichnis .

Kenorb
quelle
Ich bekomme realpath: unrecognized option '--relative-to=.':( realpath version 1.19
phuclv
@ LưuVĩnhPhúc Sie müssen die GNU / Linux-Version von verwenden realpath. Wenn Sie auf macOS sind, installieren Sie über: brew install coreutils.
Kenorb
nein, ich bin auf Ubuntu 14.04 LTS
Phuclv
@ LưuVĩnhPhúc Ich bin da, realpath (GNU coreutils) 8.26wo der Parameter vorhanden ist. Siehe: gnu.org/software/coreutils/realpath , dass Sie keinen Alias ​​verwenden (z. B. Ausführen als \realpath) und wie Sie die Version von installieren können coreutils.
Kenorb
1
Scheint realpath auf Ubuntu 14.04 ist in der Tat veraltet , und der einzige Weg ist Coreutil manuell neu zu
erstellen
7

Ich denke, diese Python-Lösung (aus dem gleichen Beitrag wie @EvanTeitelmans Antwort) ist ebenfalls erwähnenswert. Fügen Sie dies zu Ihrem hinzu ~/.zshrc:

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

Sie können dann zum Beispiel Folgendes tun:

$ relpath /usr/local/share/doc/emacs /usr/local/share/fonts
../doc/emacs
terdon
quelle
@StephaneChazelas Dies war eine direkte Kopie Einfügen aus der verknüpften Antwort. Ich bin ein Perl-Typ und kenne im Wesentlichen keine Python, also weiß ich nicht, wie man es benutzt sys.argv. Wenn der veröffentlichte Code falsch ist oder verbessert werden kann, können Sie dies mit meinem Dank tun.
Terdon
@StephaneChazelas Ich verstehe jetzt, danke für die Bearbeitung.
terdon
7

Es gibt keine Shell-Builtins, die sich darum kümmern. Ich habe jedoch eine Lösung für Stack Overflow gefunden . Ich habe die Lösung hier mit geringfügigen Änderungen kopiert und diese Antwort als Community-Wiki markiert.

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

Sie können diese Funktion folgendermaßen verwenden:

source=/foo/bar/something
target=/foo/hello/world
ln -s "$(relpath "$source" "$target")" "$source"
user26112
quelle
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/}"
}

Dies ist die einfachste und schönste Lösung für das Problem.

Es sollte auch auf allen bourne-ähnlichen Shells funktionieren.

Und die Weisheit dabei ist: Es sind absolut keine externen Dienstprogramme oder sogar komplizierten Bedingungen erforderlich. Ein paar Präfix- / Suffix-Substitutionen und eine while-Schleife sind alles, was Sie für bourne-ähnliche Shells benötigen.

Auch über PasteBin erhältlich: http://pastebin.com/CtTfvime

Arto Pekkanen
quelle
Vielen Dank für Ihre Lösung. Um dies zu ermöglichen, musste Makefileich der Zeile ein Paar doppelte Anführungszeichen pos=${pos%/*}(und natürlich Semikolons und Backslashes am Ende von everyline) hinzufügen.
Circonflexe
Idee ist nicht schlecht , aber in der aktuellen Version viele Fälle nicht funktionieren: relpath /tmp /tmp, relpath /tmp /tmp/a, relpath /tmp/a /. Außerdem vergessen Sie zu erwähnen, dass alle Pfade absolut UND kanonisiert sein müssen (dh /tmp/a/..nicht funktionieren)
Jérôme Pouiller
0

Ich musste eine schreiben Bash-Skript , um dies zuverlässig zu tun (und natürlich ohne die Existenz der Datei zu überprüfen).

Verwendung

relpath <symlink path> <source path>

Gibt einen relativen Quellpfad zurück.

Beispiel:

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

beide bekommen ../../CC/DD/EE


Um einen schnellen Test zu haben, können Sie laufen

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

Ergebnis: eine Liste von Tests

/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

Übrigens, wenn Sie dieses Skript verwenden möchten, ohne es zu speichern, und Sie diesem Skript vertrauen, haben Sie zwei Möglichkeiten, um dies mit einem Bash-Trick zu erreichen.

Importieren Sie relpathals Bash-Funktion, indem Sie dem Befehl folgen. (Benötige Bash 4+)

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

oder rufen Sie das Skript spontan auf, wie bei einer Curl-Bash-Kombination.

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
quelle