Ich habe ein kleines Bash-Skript geschrieben, um zu sehen, was passiert, wenn ich einem symbolischen Link folge, der auf dasselbe Verzeichnis verweist. Ich hatte erwartet, dass es entweder ein sehr langes Arbeitsverzeichnis erstellen oder abstürzen würde. Aber das Ergebnis hat mich überrascht…
mkdir a
cd a
ln -s ./. a
for i in `seq 1 1000`
do
cd a
pwd
done
Ein Teil der Ausgabe ist
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a
${HOME}/a/a
${HOME}/a/a/a
${HOME}/a/a/a/a
${HOME}/a/a/a/a/a
${HOME}/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a
was passiert hier?
Akzeptierte Antwort:
Patrice identifizierte die Ursache des Problems in seiner Antwort, aber wenn Sie wissen möchten, wie Sie von dort zu der Ursache gelangen, finden Sie hier die lange Geschichte.
Das aktuelle Arbeitsverzeichnis eines Prozesses ist nichts, was Sie für zu kompliziert halten würden. Es ist ein Attribut des Prozesses, der ein Handle zu einer Datei des Typs Verzeichnis ist, wo relative Pfade (in Systemaufrufen, die durch den Prozess gemacht werden) beginnen. Beim Auflösen eines relativen Pfads muss der Kernel nicht den (a) vollständigen Pfad zu diesem aktuellen Verzeichnis kennen, er liest nur die Verzeichniseinträge in dieser Verzeichnisdatei, um die erste Komponente des relativen Pfads (und ..
ist in dieser Hinsicht wie jede andere Datei) und wird von dort fortgesetzt.
Nun, als Benutzer möchten Sie manchmal wissen, wo sich dieses Verzeichnis im Verzeichnisbaum befindet. Bei den meisten Unices ist der Verzeichnisbaum ein Baum ohne Schleife. Das heißt, es gibt nur einen Pfad von der Wurzel des Baums (/
) in eine beliebige Datei. Dieser Pfad wird allgemein als kanonischer Pfad bezeichnet.
Um den Pfad des aktuellen Arbeitsverzeichnisses zu erhalten, muss ein Prozess einfach nach oben (also runter) gehen wenn Sie einen Baum mit seiner Wurzel unten sehen möchten) den Baum zurück zur Wurzel, wobei Sie die Namen der Knoten auf dem Weg finden.
Zum Beispiel versucht ein Prozess herauszufinden, dass sein aktuelles Verzeichnis /a/b/c
ist , würde den ..
öffnen Verzeichnis (relativer Pfad, also ..
ist der Eintrag im aktuellen Verzeichnis) und suchen Sie nach einer Datei vom Typ Verzeichnis mit der gleichen Inode-Nummer wie .
, finden Sie heraus, dass c
Übereinstimmungen, öffnet dann ../..
und so weiter, bis /
gefunden wird . Da gibt es keine Zweideutigkeit.
Dafür sorgt getwd()
oder getcwd()
C-Funktionen tun es oder taten es zumindest früher.
Auf einigen Systemen wie modernem Linux gibt es einen Systemaufruf, um den kanonischen Pfad zum aktuellen Verzeichnis zurückzugeben, der diese Suche im Kernelbereich durchführt (und es Ihnen ermöglicht, Ihr aktuelles Verzeichnis zu finden, auch wenn Sie keinen Lesezugriff auf alle seine Komponenten haben). , und genau das ist getcwd()
ruft dort an. Auf modernen Linux finden Sie den Pfad zum aktuellen Verzeichnis auch über einen readlink() auf /proc/self/cwd
.
Das machen die meisten Sprachen und frühen Shells, wenn sie den Pfad zum aktuellen Verzeichnis zurückgeben.
In Ihrem Fall können Sie cd a
aufrufen beliebig oft, denn es ist ein Symlink auf .
, das aktuelle Verzeichnis ändert sich nicht, also alles von getcwd()
, pwd -P
, python -c 'import os; print os.getcwd()'
, perl -MPOSIX -le 'print getcwd'
würde Ihren ${HOME}
zurückgeben .
Nun, Symlinks machten das alles noch komplizierter.
symlinks
Sprünge im Verzeichnisbaum zulassen. Unter /a/b/c
, falls /a
oder /a/b
oder /a/b/c
ein Symlink ist, dann der kanonische Pfad von /a/b/c
wäre was ganz anderes. Insbesondere die ..
Eintrag in /a/b/c
ist nicht unbedingt /a/b
.
In der Bourne-Shell, wenn Sie dies tun:
cd /a/b/c
cd ..
Oder sogar:
cd /a/b/c/..
Es gibt keine Garantie, dass Sie in /a/b
landen .
Genau wie:
vi /a/b/c/../d
ist nicht unbedingt dasselbe wie:
vi /a/b/d
ksh
führte ein Konzept eines logischen aktuellen Arbeitsverzeichnisses ein um das irgendwie zu umgehen. Die Leute gewöhnten sich daran und schließlich spezifizierte POSIX dieses Verhalten, was bedeutet, dass die meisten Shells es heutzutage auch tun:
Für die cd
und pwd
eingebaute Befehle (und nur für sie (allerdings auch für popd
/pushd
auf Shells, die sie haben)), behält die Shell ihre eigene Vorstellung vom aktuellen Arbeitsverzeichnis bei. Es wird im $PWD
gespeichert spezielle Variable.
Wenn Sie dies tun:
cd c/d
auch wenn c
oder c/d
sind Symlinks, während $PWD
enthält /a/b
, es hängt c/d
an bis zum Ende also $PWD
wird zu /a/b/c/d
. Und wenn Sie das tun:
cd ../e
Anstelle von chdir("../e")
, es tut chdir("/a/b/c/e")
.
Und das pwd
Der Befehl gibt nur den Inhalt von $PWD
zurück Variable.
Das ist in interaktiven Shells nützlich, weil pwd
gibt einen Pfad zum aktuellen Verzeichnis aus, der Auskunft darüber gibt, wie Sie dorthin gelangt sind und solange Sie nur ..
verwenden in Argumenten zu cd
und nicht andere Befehle, ist es weniger wahrscheinlich, dass Sie überrascht werden, weil cd a; cd ..
oder cd a/..
würde Sie im Allgemeinen dorthin zurückbringen, wo Sie waren.
Nun, $PWD
wird nicht geändert, es sei denn, Sie machen ein cd
. Bis zum nächsten Aufruf von cd
oder pwd
, viele Dinge könnten passieren, jede der Komponenten von $PWD
umbenannt werden könnte. Das aktuelle Verzeichnis ändert sich nie (es ist immer derselbe Inode, obwohl er gelöscht werden könnte), aber sein Pfad im Verzeichnisbaum könnte sich vollständig ändern. getcwd()
berechnet das aktuelle Verzeichnis jedes Mal, wenn es aufgerufen wird, indem es den Verzeichnisbaum hinuntergeht, sodass seine Informationen immer korrekt sind, aber für das logische Verzeichnis, das von POSIX-Shells implementiert wird, die Informationen in $PWD
könnte alt werden. Also beim Ausführen von cd
oder pwd
, einige Muscheln möchten sich vielleicht davor schützen.
In diesem speziellen Fall sehen Sie unterschiedliche Verhaltensweisen mit unterschiedlichen Shells.
Manche mögen ksh93
Ignorieren Sie das Problem vollständig, so dass auch nach dem Aufruf von cd
falsche Informationen zurückgegeben werden (und Sie würden nicht das Verhalten sehen, das Sie mit bash
sehen dort).
Manche mögen bash
oder zsh
überprüfen Sie das $PWD
ist immer noch ein Pfad zum aktuellen Verzeichnis auf cd
, aber nicht auf pwd
.
pdksh überprüft beide pwd
und cd
(aber nach pwd
, aktualisiert $PWD
nicht )
ash
(zumindest die auf Debian gefundene) überprüft nicht, und wenn Sie cd a
ausführen , es macht tatsächlich cd "$PWD/a"
, also wenn sich das aktuelle Verzeichnis geändert hat und $PWD
nicht mehr auf das aktuelle Verzeichnis zeigt, wird es tatsächlich nicht zu a
wechseln Verzeichnis im aktuellen Verzeichnis, sondern das in $PWD
(und einen Fehler zurückgeben, wenn er nicht vorhanden ist).
Wenn Sie damit spielen möchten, können Sie Folgendes tun:
cd
mkdir -p a/b
cd a
pwd
mv ~/a ~/b
pwd
echo "$PWD"
cd b
pwd; echo "$PWD"; pwd -P # (and notice the bug in ksh93)
in verschiedenen Muscheln.
In Ihrem Fall, da Sie bash
verwenden , nach einem cd a
, bash
prüft, ob $PWD
zeigt immer noch auf das aktuelle Verzeichnis. Dazu ruft es stat()
auf auf den Wert von $PWD
um seine Inode-Nummer zu prüfen und mit der von .
zu vergleichen .
Aber beim Nachschlagen von $PWD
path beinhaltet das Auflösen zu vieler Symlinks, die stat()
kehrt mit einem Fehler zurück, sodass die Shell nicht prüfen kann, ob $PWD
entspricht immer noch dem aktuellen Verzeichnis, also wird es mit getcwd()
neu berechnet und aktualisiert $PWD
entsprechend.
Um Patrices Antwort zu verdeutlichen:Diese Überprüfung der Anzahl der Symlinks, die beim Suchen eines Pfads angetroffen werden, dient dem Schutz vor Symlink-Schleifen. Die einfachste Schleife kann mit
erstellt werdenrm -f a b
ln -s a b
ln -s b a
Ohne diesen Schutz, bei einem cd a/x
, müsste das System finden, wo a
verlinkt auf, findet es b
und ist ein Symlink, der auf a
verweist , und das würde auf unbestimmte Zeit so weitergehen. Der einfachste Weg, sich davor zu schützen, besteht darin, aufzugeben, nachdem mehr als eine beliebige Anzahl von symbolischen Links aufgelöst wurde.
Nun zurück zum logischen aktuellen Arbeitsverzeichnis und warum es kein so gutes Feature ist. Es ist wichtig zu wissen, dass es nur für cd
gilt in der Shell und nicht in anderen Befehlen.
Zum Beispiel:
cd -- "$dir" && vi -- "$file"
ist nicht immer dasselbe wie:
vi -- "$dir/$file"
Aus diesem Grund werden Sie manchmal feststellen, dass die Leute empfehlen, immer cd -P
zu verwenden in Skripten, um Verwirrung zu vermeiden (Sie möchten nicht, dass Ihre Software ein Argument von ../x
verarbeitet anders als andere Befehle, nur weil es in der Shell statt in einer anderen Sprache geschrieben ist).
Das -P
Option ist, das logische Verzeichnis zu deaktivieren Handhabung so cd -P -- "$var"
ruft tatsächlich chdir()
auf auf den Inhalt von $var
(mindestens solange $CDPATH
es nicht gesetzt, und außer wenn $var
ist -
(oder möglicherweise -2
, +3
… in manchen Schalen) aber das ist eine andere Geschichte). Und nach einem cd -P
, $PWD
enthält einen kanonischen Pfad.