Beispiel:
absolute="/foo/bar"
current="/foo/baz/foo"
# Magic
relative="../../bar"
Wie erstelle ich die Magie (hoffentlich nicht zu komplizierter Code ...)?
bash
shell
path
relative-path
absolute-path
Paul Tarjan
quelle
quelle
realpath --relative-to=$absolute $current
.Antworten:
Die Verwendung von realpath aus GNU coreutils 8.23 ist meiner Meinung nach am einfachsten:
Beispielsweise:
quelle
$ realpath --relative-to="${PWD}" "$file"
ist nützlich, wenn Sie die Pfade relativ zum aktuellen Arbeitsverzeichnis möchten./usr/bin/nmap/
Pfades, aber nicht für/usr/bin/nmap
: vonnmap
bis ist/tmp/testing
es nur../../
und nicht dreimal../
. Es funktioniert jedoch, weil..
auf dem rootfs zu tun ist/
.--relative-to=…
erwartet ein Verzeichnis und prüft NICHT. Das bedeutet, dass Sie ein zusätzliches "../" erhalten, wenn Sie einen Pfad relativ zu einer Datei anfordern (wie dieses Beispiel zu tun scheint, da es/usr/bin
selten oder nie Verzeichnisse enthält undnmap
normalerweise eine Binärdatei ist)gibt:
quelle
relpath(){ python -c "import os.path; print os.path.relpath('$1','${2:-$PWD}')" ; }
python -c 'import os, sys; print(os.path.relpath(*sys.argv[1:]))'
funktioniert am natürlichsten und zuverlässigsten.Dies ist eine korrigierte, voll funktionsfähige Verbesserung der derzeit am besten bewerteten Lösung von @pini (die leider nur wenige Fälle behandelt).
Erinnerung: '-z' testet, ob die Zeichenfolge die Länge Null hat (= leer) und '-n' testet, ob die Zeichenfolge nicht leer ist.
Testfälle:
quelle
source=$1; target=$2
durchsource=$(realpath $1); target=$(realpath $2)
realpath
wird empfohlen, odersource=$(readlink -f $1)
etc., wenn realpath nicht verfügbar ist (nicht Standard)$source
und$target
mag dies: `if [[-e $ 1]]; dann source = $ (readlink -f $ 1); sonst Quelle = $ 1; fi if [[-e $ 2]]; dann target = $ (readlink -f $ 2); sonst Ziel = $ 2; Auf diese Weise könnte die Funktion sowohl reale / existierende relative Pfade als auch fiktive Verzeichnisse verarbeiten.readlink
hat eine-m
Option, die genau das tut;)quelle
Es ist seit 2001 in Perl integriert und funktioniert daher auf nahezu jedem erdenklichen System, sogar auf VMS .
Auch die Lösung ist leicht zu verstehen.
Also für Ihr Beispiel:
... würde gut funktionieren.
quelle
say
war in perl nicht als Protokoll verfügbar, könnte aber hier effektiv verwendet werden.perl -MFile::Spec -E 'say File::Spec->abs2rel(@ARGV)'
perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target"
undperl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target" "$origin"
. Das erste einzeilige Perl- Skript verwendet ein Argument (Ursprung ist das aktuelle Arbeitsverzeichnis). Das zweite einzeilige Perl- Skript verwendet zwei Argumente.perl
kann fast überall gefunden werden, obwohl die Antwort immer noch einzeilig ist.Vorausgesetzt, Sie haben Folgendes installiert: bash, pwd, dirname, echo; dann ist relpath
Ich habe die Antwort von golfed Pini und ein paar anderen Ideen
Hinweis : Dies erfordert, dass beide Pfade vorhandene Ordner sind. Dateien funktionieren nicht .
quelle
Python
os.path.relpath
als Shell-FunktionDas Ziel dieser
relpath
Übung ist esos.path.relpath
, die von xni vorgeschlagene Funktion von Python 2.7 nachzuahmen (verfügbar ab Python Version 2.6, funktioniert aber nur in 2.7 ordnungsgemäß) . Infolgedessen können einige der Ergebnisse von den in anderen Antworten angegebenen Funktionen abweichen.(Ich habe nicht mit Zeilenumbrüchen in Pfaden getestet, nur weil dadurch die Validierung aufgrund eines Aufrufs
python -c
von ZSH unterbrochen wird. Dies wäre sicherlich mit einigem Aufwand möglich.)In Bezug auf „Magie“ in Bash habe ich es längst aufgegeben, in Bash nach Magie zu suchen, aber seitdem habe ich alle Magie gefunden, die ich brauche, und noch einige mehr in ZSH.
Folglich schlage ich zwei Implementierungen vor.
Die erste Implementierung soll vollständig POSIX-konform sein . Ich habe es mit
/bin/dash
Debian 6.0.6 "Squeeze" getestet . Es funktioniert auch perfekt mit/bin/sh
OS X 10.8.3, bei dem es sich tatsächlich um Bash Version 3.2 handelt, die vorgibt, eine POSIX-Shell zu sein.Die zweite Implementierung ist eine ZSH-Shell-Funktion, die robust gegen mehrere Schrägstriche und andere Belästigungen in Pfaden ist. Wenn Sie ZSH zur Verfügung haben, ist dies die empfohlene Version, auch wenn Sie sie in der unten dargestellten Skriptform (dh mit einem Shebang von
#!/usr/bin/env zsh
) aus einer anderen Shell aufrufen .Schließlich habe ich ein ZSH-Skript geschrieben, das die Ausgabe des
relpath
Befehls überprüft, der in$PATH
den in anderen Antworten angegebenen Testfällen gefunden wurde . Ich habe diesen Tests etwas Würze hinzugefügt, indem ich Leerzeichen, Tabulatoren und Interpunktion wie! ? *
hier und da hinzugefügt habe, und auch einen weiteren Test mit exotischen UTF-8-Zeichen in vim-powerline durchgeführt .POSIX- Shell-Funktion
Erstens die POSIX-kompatible Shell-Funktion. Es funktioniert mit einer Vielzahl von Pfaden, bereinigt jedoch nicht mehrere Schrägstriche und löst keine Symlinks auf.
ZSH-Shell-Funktion
Nun die robustere
zsh
Version. Wenn Sie möchten, dass die Argumente in reale Pfade à la aufgelöst werdenrealpath -f
(im Linux-coreutils
Paket verfügbar ), ersetzen Sie die:a
Zeilen 3 und 4 durch:A
.Um dies in zsh zu verwenden, entfernen Sie die erste und letzte Zeile und legen Sie sie in einem Verzeichnis ab, das sich in Ihrer
$FPATH
Variablen befindet.Testskript
Zum Schluss das Testskript. Es wird eine Option akzeptiert, nämlich
-v
die ausführliche Ausgabe zu aktivieren.quelle
/
.Das obige Shell-Skript wurde von Pinis inspiriert (Danke!). Es löst einen Fehler im Syntaxhervorhebungsmodul von Stack Overflow aus (zumindest in meinem Vorschaurahmen). Bitte ignorieren Sie, wenn die Hervorhebung falsch ist.
Einige Notizen:
Mit Ausnahme der genannten Backslash-Sequenzen gibt die letzte Funktionszeile "relPath" Pfadnamen aus, die mit Python kompatibel sind:
Die letzte Zeile kann durch eine Zeile ersetzt (und vereinfacht) werden
Ich bevorzuge letzteres, weil
Dateinamen können direkt an von relPath erhaltene Verzeichnispfade angehängt werden, z.
Bei symbolischen Links im selben Verzeichnis, die mit dieser Methode erstellt wurden
"./"
, wird dem Dateinamen nicht das Hässliche vorangestellt.Codeauflistung für Regressionstests (einfach an das Shell-Skript anhängen):
quelle
Nicht viele der Antworten hier sind für den täglichen Gebrauch praktisch. Da es sehr schwierig ist, dies in reiner Bash richtig zu machen, schlage ich die folgende zuverlässige Lösung vor (ähnlich einem in einem Kommentar vergrabenen Vorschlag):
Anschließend können Sie den relativen Pfad basierend auf dem aktuellen Verzeichnis abrufen:
oder Sie können angeben, dass der Pfad relativ zu einem bestimmten Verzeichnis ist:
Der einzige Nachteil ist, dass dies Python erfordert, aber:
Beachten Sie, dass Lösungen, die enthalten sind
basename
oderdirname
nicht unbedingt besser sein müssen, da sie einecoreutils
Installation erfordern . Wenn jemand eine reinebash
Lösung hat, die zuverlässig und einfach ist (und keine verworrene Neugier), wäre ich überrascht.quelle
Dieses Skript liefert korrekte Ergebnisse nur für Eingaben, die absolute Pfade oder relative Pfade ohne
.
oder sind..
:quelle
Ich würde Perl nur für diese nicht so triviale Aufgabe verwenden:
quelle
perl -MFile::Spec -e "print File::Spec->abs2rel('$absolute','$current')"
damit absolut und aktuell angegeben werden.relative=$(perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$absolute" "$current")
. Dies stellt sicher, dass die Werte selbst keinen Perl-Code enthalten können!Eine leichte Verbesserung der Antworten von Kasku und Pini , die besser mit Leerzeichen spielt und das Übergeben relativer Pfade ermöglicht:
quelle
test.sh:
Testen:
quelle
readlink
auf$(pwd)
.Noch eine andere Lösung, pure
bash
+ GNUreadlink
für die einfache Verwendung in folgendem Kontext:Dies funktioniert unter fast allen aktuellen Linux. Wenn
readlink -m
es an Ihrer Seite nicht funktioniert, versuchen Sie esreadlink -f
stattdessen. Siehe auch https://gist.github.com/hilbix/1ec361d00a8178ae8ea0 für mögliche Updates:Anmerkungen:
*
oder enthalten?
.ln -s
:relpath / /
gibt.
und nicht die leere Zeichenfolgerelpath a a
gibta
, auch wenna
zufällig ein Verzeichnis istreadlink
erforderlich, um Pfade zu kanonisieren.readlink -m
dessen funktioniert es auch für noch nicht vorhandene Pfade.Auf alten Systemen, auf denen
readlink -m
keine verfügbar ist,readlink -f
schlägt dies fehl, wenn die Datei nicht vorhanden ist. Sie benötigen also wahrscheinlich eine Problemumgehung wie diese (ungetestet!):Dies ist nicht wirklich richtig, wenn
$1
Includes.
oder..
nicht vorhandene Pfade enthalten sind (wie in/doesnotexist/./a
) enthalten sind, sollte aber die meisten Fälle abdecken.(Ersetzen Sie
readlink -m --
oben durchreadlink_missing
.)Bearbeiten wegen der folgenden Downvote
Hier ist ein Test, dass diese Funktion tatsächlich korrekt ist:
Verwirrt? Nun, das sind die richtigen Ergebnisse ! Auch wenn Sie der Meinung sind, dass es nicht zur Frage passt, ist hier der Beweis, dass dies richtig ist:
Ohne Zweifel
../bar
ist der genaue und einzige korrekte relative Pfad der Seitebar
von der Seite aus zu sehenmoo
. Alles andere wäre einfach falsch.Es ist trivial, die Ausgabe auf die Frage zu übernehmen, die anscheinend davon ausgeht, dass
current
es sich um ein Verzeichnis handelt:Dies gibt genau das zurück, wonach gefragt wurde.
Und bevor Sie eine Augenbraue hochziehen, ist hier eine etwas komplexere Variante von
relpath
(erkennen Sie den kleinen Unterschied), die auch für die URL-Syntax funktionieren sollte (so dass ein Trailing/
dank einigerbash
Magier überlebt ):Und hier sind die Überprüfungen, um klar zu machen: Es funktioniert wirklich wie gesagt.
Und so kann dies verwendet werden, um das gewünschte Ergebnis aus der Frage zu erhalten:
Wenn Sie etwas finden, das nicht funktioniert, lassen Sie es mich bitte in den Kommentaren unten wissen. Vielen Dank.
PS:
Warum sind die Argumente von
relpath
"umgekehrt" im Gegensatz zu allen anderen Antworten hier?Wenn Sie sich ändern
zu
Dann können Sie den zweiten Parameter weglassen, sodass die BASE das aktuelle Verzeichnis / URL / was auch immer ist. Das ist wie immer nur das Unix-Prinzip.
Wenn Ihnen das nicht gefällt, kehren Sie bitte zu Windows zurück. Vielen Dank.
quelle
Leider scheint Mark Rushakoffs Antwort (jetzt gelöscht - sie verweist auf den Code von hier ) nicht richtig zu funktionieren, wenn sie angepasst wird an:
Das im Kommentar skizzierte Denken kann verfeinert werden, damit es in den meisten Fällen richtig funktioniert. Ich gehe davon aus, dass das Skript ein Quellargument (wo Sie sich befinden) und ein Zielargument (wo Sie hin wollen) verwendet und dass entweder beide absolute Pfadnamen oder beide relativ sind. Wenn einer absolut und der andere relativ ist, ist es am einfachsten, dem relativen Namen das aktuelle Arbeitsverzeichnis voranzustellen - der folgende Code tut dies jedoch nicht.
In acht nehmen
Der folgende Code funktioniert fast korrekt, ist aber nicht ganz richtig.
xyz/./pqr
'.xyz/../pqr
'../
' aus Pfaden.Dennis 'Code ist besser, weil er 1 und 5 behebt - hat aber die gleichen Probleme 2, 3, 4. Verwenden Sie deshalb Dennis' Code (und stimmen Sie ihn vorher ab).
(Hinweis: POSIX bietet einen Systemaufruf
realpath()
, mit dem Pfadnamen aufgelöst werden, sodass keine Symlinks mehr vorhanden sind. Wenn Sie diese auf die Eingabenamen anwenden und dann Dennis 'Code verwenden, erhalten Sie jedes Mal die richtige Antwort. Es ist trivial, den C-Code zu schreiben Wrapsrealpath()
- ich habe es geschafft - aber ich kenne kein Standarddienstprogramm, das dies tut.)Aus diesem Grund finde ich Perl einfacher zu verwenden als Shell, obwohl bash Arrays anständig unterstützt und dies wahrscheinlich auch tun könnte - Übung für den Leser. Teilen Sie sie also bei zwei kompatiblen Namen jeweils in Komponenten auf:
So:
Testskript (die eckigen Klammern enthalten ein Leerzeichen und eine Registerkarte):
Ausgabe aus dem Testskript:
Dieses Perl-Skript funktioniert unter Unix ziemlich gründlich (es berücksichtigt nicht alle Komplexitäten von Windows-Pfadnamen) angesichts seltsamer Eingaben. Es verwendet das Modul
Cwd
und seine Funktionrealpath
, um den tatsächlichen Pfad der vorhandenen Namen aufzulösen, und führt eine Textanalyse für nicht vorhandene Pfade durch. In allen Fällen außer einem erzeugt es die gleiche Ausgabe wie Dennis 'Skript. Der abweichende Fall ist:Die beiden Ergebnisse sind gleichwertig - nur nicht identisch. (Die Ausgabe stammt aus einer leicht modifizierten Version des Testskripts. Das folgende Perl-Skript druckt einfach die Antwort und nicht die Eingaben und die Antwort wie im obigen Skript.) Nun: Sollte ich die nicht funktionierende Antwort entfernen? Vielleicht...
quelle
/home/part1/part2
zu/
hat man zu viele../
. Andernfalls stimmt mein Skript mit Ihrer Ausgabe überein, außer dass mein Skript.
am Ende des Ziels, in dem sich das Ziel befindet, ein unnötiges hinzufügt,.
und ich verwende./
am Anfang kein Skript , das absteigt, ohne nach oben zu gehen.readlink /usr/bin/vi
gibt/etc/alternatives/vi
, aber das ist ein weiterer Symlink - währendreadlink -f /usr/bin/vi
gibt/usr/bin/vim.basic
, was das ultimative Ziel aller Symlinks ist ...Ich nahm Ihre Frage als Herausforderung, dies in "tragbaren" Shell-Code zu schreiben, dh
Es läuft auf jeder POSIX-konformen Shell (zsh, bash, ksh, ash, Busybox, ...). Es enthält sogar eine Testsuite, um den Betrieb zu überprüfen. Die Kanonisierung von Pfadnamen bleibt als Übung. :-)
quelle
Meine Lösung:
quelle
Hier ist meine Version. Es basiert auf der Antwort von @Offirmo . Ich habe es Dash-kompatibel gemacht und den folgenden Testfallfehler behoben:
./compute-relative.sh "/a/b/c/de/f/g" "/a/b/c/def/g/"
->"../..f/g/"
Jetzt:
CT_FindRelativePath "/a/b/c/de/f/g" "/a/b/c/def/g/"
->"../../../def/g/"
Siehe den Code:
quelle
Ich denke, dieser soll auch den Trick machen ... (kommt mit eingebauten Tests) :)
OK, etwas Overhead erwartet, aber wir machen hier Bourne Shell! ;)
quelle
Dieses Skript funktioniert nur mit den Pfadnamen. Es ist nicht erforderlich, dass eine der Dateien vorhanden ist. Wenn die übergebenen Pfade nicht absolut sind, ist das Verhalten etwas ungewöhnlich, aber es sollte wie erwartet funktionieren, wenn beide Pfade relativ sind.
Ich habe es nur unter OS X getestet, daher ist es möglicherweise nicht portabel.
quelle
Diese Antwort behandelt nicht den Bash-Teil der Frage, aber da ich versucht habe, die Antworten in dieser Frage zu verwenden, um diese Funktionalität in Emacs zu implementieren, werde ich sie dort rauswerfen.
Emacs hat tatsächlich eine sofort einsatzbereite Funktion:
quelle
relpath
Funktion),file-relative-name
für die von Ihnen bereitgestellten Testfälle identisch verhält .Hier ist ein Shell-Skript, das dies tut, ohne andere Programme aufzurufen:
Quelle: http://www.ynform.org/w/Pub/Relpath
quelle
Ich brauchte so etwas, aber das löste auch symbolische Links auf. Ich habe festgestellt, dass pwd zu diesem Zweck ein -P-Flag hat. Ein Fragment meines Skripts ist angehängt. Es befindet sich innerhalb einer Funktion in einem Shell-Skript, daher die $ 1 und $ 2. Der Ergebniswert, der der relative Pfad von START_ABS zu END_ABS ist, befindet sich in der Variablen UPDIRS. Die Skript-CDs befinden sich in jedem Parameterverzeichnis, um das pwd -P auszuführen, und dies bedeutet auch, dass relative Pfadparameter behandelt werden. Prost, Jim
quelle