Ho scritto un piccolo script bash per vedere cosa succede quando continuo a seguire un collegamento simbolico che punta alla stessa directory. Mi aspettavo che creasse una directory di lavoro molto lunga o si bloccasse. Ma il risultato mi ha sorpreso...
mkdir a
cd a
ln -s ./. a
for i in `seq 1 1000`
do
cd a
pwd
done
Parte dell'output è
${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
cosa sta succedendo qui?
Risposta accettata:
Patrice ha identificato l'origine del problema nella sua risposta, ma se vuoi sapere come arrivare da lì al perché lo ottieni, ecco la lunga storia.
L'attuale directory di lavoro di un processo non è nulla di troppo complicato. È un attributo del processo che è un handle per un file di tipo directory da cui iniziano i percorsi relativi (nelle chiamate di sistema effettuate dal processo). Quando risolve un percorso relativo, il kernel non ha bisogno di conoscere il (a) percorso completo di quella directory corrente, legge semplicemente le voci di directory in quel file di directory per trovare il primo componente del percorso relativo (e ..
è come qualsiasi altro file al riguardo) e continua da lì.
Ora, come utente, a volte ti piace sapere dove si trova quella directory nell'albero delle directory. Con la maggior parte degli Unice, l'albero delle directory è un albero, senza loop. Cioè, c'è solo un percorso dalla radice dell'albero (/
) in un determinato file. Quel percorso è generalmente chiamato percorso canonico.
Per ottenere il percorso della directory di lavoro corrente, ciò che un processo deve fare è semplicemente salire (beh giù se ti piace vedere un albero con la sua radice in basso) l'albero torna alla radice, trovando i nomi dei nodi lungo il percorso.
Ad esempio, un processo che cerca di scoprire che la sua directory corrente è /a/b/c
, aprirebbe il ..
directory (percorso relativo, quindi ..
è la voce nella directory corrente) e cerca un file di tipo directory con lo stesso numero di inode di .
, scopri che c
corrisponde, quindi apre ../..
e così via finché non trova /
. Non c'è ambiguità lì.
Questo è ciò che il getwd()
o getcwd()
Le funzioni C lo fanno o almeno lo facevano.
Su alcuni sistemi come il moderno Linux, c'è una chiamata di sistema per restituire il percorso canonico alla directory corrente che fa quella ricerca nello spazio del kernel (e ti permette di trovare la tua directory corrente anche se non hai accesso in lettura a tutti i suoi componenti) , ed è quello che getcwd()
chiama lì. Su Linux moderno, puoi anche trovare il percorso della directory corrente tramite readlink() su /proc/self/cwd
.
Questo è ciò che fanno la maggior parte delle lingue e delle prime shell quando restituiscono il percorso alla directory corrente.
Nel tuo caso, puoi chiamare cd a
quante volte vuoi, perché è un collegamento simbolico a .
, la directory corrente non cambia, quindi tutto getcwd()
, pwd -P
, python -c 'import os; print os.getcwd()'
, perl -MPOSIX -le 'print getcwd'
restituirebbe il tuo ${HOME}
.
Ora, i collegamenti simbolici hanno complicato tutto questo.
symlinks
consentire i salti nell'albero delle directory. In /a/b/c
, se /a
o /a/b
o /a/b/c
è un collegamento simbolico, quindi il percorso canonico di /a/b/c
sarebbe qualcosa di completamente diverso. In particolare, il ..
voce in /a/b/c
non è necessariamente /a/b
.
Nella shell Bourne, se lo fai:
cd /a/b/c
cd ..
O anche:
cd /a/b/c/..
Non c'è alcuna garanzia che finirai in /a/b
.
Proprio come:
vi /a/b/c/../d
non è necessariamente uguale a:
vi /a/b/d
ksh
introdotto un concetto di directory di lavoro corrente logica per aggirare in qualche modo questo. Le persone si sono abituate e POSIX ha finito per specificare quel comportamento, il che significa che anche la maggior parte delle shell al giorno d'oggi lo fa:
Per il cd
e pwd
comandi integrati (e solo per loro (anche se anche per popd
/pushd
su shell che li hanno)), la shell mantiene la propria idea della directory di lavoro corrente. È memorizzato nel $PWD
variabile speciale.
Quando lo fai:
cd c/d
anche se c
o c/d
sono collegamenti simbolici, mentre $PWD
contiene /a/b
, aggiunge c/d
alla fine quindi $PWD
diventa /a/b/c/d
. E quando lo fai:
cd ../e
Invece di fare chdir("../e")
, fa chdir("/a/b/c/e")
.
E il pwd
il comando restituisce solo il contenuto del $PWD
variabile.
È utile nelle shell interattive perché pwd
restituisce un percorso alla directory corrente che fornisce informazioni su come ci sei arrivato e purché utilizzi solo ..
negli argomenti di cd
e non altri comandi, è meno probabile che ti sorprenda, perché cd a; cd ..
o cd a/..
generalmente ti riporterebbe dove eri.
Ora, $PWD
non viene modificato a meno che tu non faccia un cd
. Alla prossima volta che chiamerai cd
o pwd
, potrebbero succedere molte cose, qualsiasi componente di $PWD
potrebbe essere rinominato. La directory corrente non cambia mai (è sempre lo stesso inode, anche se potrebbe essere cancellato), ma il suo percorso nell'albero delle directory potrebbe cambiare completamente. getcwd()
calcola la directory corrente ogni volta che viene chiamata camminando lungo l'albero delle directory in modo che le sue informazioni siano sempre accurate, ma per la directory logica implementata dalle shell POSIX, le informazioni in $PWD
potrebbe diventare stantio. Quindi, dopo aver eseguito cd
o pwd
, alcune shell potrebbero voler proteggersi da questo.
In quel caso particolare, vedi comportamenti diversi con shell diverse.
Alcuni come ksh93
ignora completamente il problema, quindi restituirà informazioni errate anche dopo aver chiamato cd
(e non vedresti il comportamento che stai vedendo con bash
lì).
Ad alcuni piace bash
o zsh
controlla che $PWD
è ancora un percorso alla directory corrente su cd
, ma non su pwd
.
pdksh controlla entrambi pwd
e cd
(ma su pwd
, non aggiorna $PWD
)
ash
(almeno quello trovato su Debian) non controlla, e quando lo fai cd a
, in realtà esegue cd "$PWD/a"
, quindi se la directory corrente è cambiata e $PWD
non punta più alla directory corrente, in realtà non cambierà in a
directory nella directory corrente, ma quella in $PWD
(e restituisce un errore se non esiste).
Se vuoi giocarci, puoi fare:
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 vari gusci.
Nel tuo caso, dato che stai usando bash
, dopo un cd a
, bash
controlla che $PWD
punta ancora alla directory corrente. Per farlo, chiama stat()
sul valore di $PWD
per controllare il suo numero di inode e confrontarlo con quello di .
.
Ma quando si cerca il $PWD
path implica la risoluzione di troppi collegamenti simbolici, che stat()
restituisce con un errore, quindi la shell non può verificare se $PWD
corrisponde ancora alla directory corrente, quindi la calcola nuovamente con getcwd()
e aggiorna $PWD
di conseguenza.
Ora, per chiarire la risposta di Patrice, il controllo del numero di collegamenti simbolici incontrati durante la ricerca di un percorso serve a proteggersi dai loop di collegamenti simbolici. Il ciclo più semplice può essere realizzato con
rm -f a b
ln -s a b
ln -s b a
Senza quella protezione, su un cd a/x
, il sistema dovrebbe trovare dove a
link a, trova che è b
ed è un collegamento simbolico che rimanda a a
, e questo andrebbe avanti all'infinito. Il modo più semplice per evitarlo è rinunciare dopo aver risolto più di un numero arbitrario di collegamenti simbolici.
Ora torniamo alla directory di lavoro corrente logica e perché non è una caratteristica così buona. È importante rendersi conto che è solo per cd
nella shell e non in altri comandi.
Ad esempio:
cd -- "$dir" && vi -- "$file"
non è sempre uguale a:
vi -- "$dir/$file"
Ecco perché a volte scoprirai che le persone consigliano di usare sempre cd -P
negli script per evitare confusione (non vuoi che il tuo software gestisca un argomento di ../x
diversamente dagli altri comandi solo perché è scritto in shell invece che in un'altra lingua).
Il -P
l'opzione è disabilitare la directory logica gestendo così cd -P -- "$var"
in realtà chiama chdir()
sul contenuto di $var
(almeno finché $CDPATH
non è impostato, e tranne quando $var
è -
(o possibilmente -2
, +3
... in alcuni gusci) ma questa è un'altra storia). E dopo un cd -P
, $PWD
conterrà un percorso canonico.