Napsal jsem malý bash skript, abych viděl, co se stane, když budu sledovat symbolický odkaz, který ukazuje na stejný adresář. Čekal jsem, že buď vytvoří velmi dlouhý pracovní adresář, nebo se zhroutí. Ale výsledek mě překvapil…
mkdir a
cd a
ln -s ./. a
for i in `seq 1 1000`
do
cd a
pwd
done
Část výstupu je
${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
co se tady děje?
Přijatá odpověď:
Patrice ve své odpovědi identifikoval zdroj problému, ale pokud chcete vědět, jak se odtamtud dostat k tomu, proč tomu tak je, zde je dlouhý příběh.
Aktuální pracovní adresář procesu není nic, co byste považovali za příliš složité. Je to atribut procesu, který je popisovačem souboru typu adresář, odkud začínají relativní cesty (v systémových voláních prováděných procesem). Při řešení relativní cesty jádro nepotřebuje znát (a) úplnou cestu k tomuto aktuálnímu adresáři, pouze čte položky adresáře v tomto adresářovém souboru, aby našlo první komponentu relativní cesty (a ..
je v tomto ohledu jako jakýkoli jiný soubor) a pokračuje odtud.
Nyní, jako uživatel, byste někdy rádi věděli, kde tento adresář v adresářovém stromu leží. U většiny Unices je strom adresářů strom bez smyčky. To znamená, že existuje pouze jedna cesta z kořene stromu (/
) do libovolného souboru. Tato cesta se obecně nazývá kanonická cesta.
Aby proces získal cestu k aktuálnímu pracovnímu adresáři, musí jít nahoru (dobře dolů pokud chcete vidět strom s kořenem dole), strom zpět ke kořenu a najděte jména uzlů na cestě.
Například proces, který se snaží zjistit, že jeho aktuální adresář je /a/b/c
, by otevřel ..
adresář (relativní cesta, takže ..
je záznam v aktuálním adresáři) a vyhledejte soubor typu adresář se stejným číslem inodu jako .
, zjistěte, že c
odpovídá a poté otevře ../..
a tak dále, dokud nenajde /
. Není v tom žádná dvojznačnost.
To je to, co getwd()
nebo getcwd()
C funkce dělají nebo alespoň dělaly.
Na některých systémech, jako je moderní Linux, existuje systémové volání, které vrátí kanonickou cestu do aktuálního adresáře, což provede toto vyhledávání v prostoru jádra (a umožní vám najít váš aktuální adresář, i když nemáte přístup ke všem jeho komponentám) , a to je to, co getcwd()
tam volá. Na moderním Linuxu můžete také najít cestu k aktuálnímu adresáři pomocí readlink() na /proc/self/cwd
.
To je to, co dělá většina jazyků a prvních shellů, když vrací cestu do aktuálního adresáře.
Ve vašem případě můžete zavolat cd a
kolikrát budete chtít, protože je to symbolický odkaz na .
, aktuální adresář se nemění, takže všechny getcwd()
, pwd -P
, python -c 'import os; print os.getcwd()'
, perl -MPOSIX -le 'print getcwd'
vrátí váš ${HOME}
.
Nyní to vše zkomplikovaly symbolické odkazy.
symlinks
povolit skoky v adresářovém stromu. V /a/b/c
, pokud /a
nebo /a/b
nebo /a/b/c
je symbolický odkaz, pak kanonická cesta /a/b/c
by bylo něco úplně jiného. Konkrétně ..
záznam v /a/b/c
není nutně /a/b
.
V Bourne shell, pokud tak učiníte:
cd /a/b/c
cd ..
Nebo dokonce:
cd /a/b/c/..
Neexistuje žádná záruka, že skončíte v /a/b
.
Stejně jako:
vi /a/b/c/../d
není nutně totéž jako:
vi /a/b/d
ksh
představil koncept logického aktuálního pracovního adresáře nějak to obejít. Lidé si na to zvykli a POSIX nakonec specifikoval toto chování, což znamená, že většina současných shellů to dělá také:
Pro cd
a pwd
vestavěné příkazy (a pouze pro ně (i když také pro popd
/pushd
na shellech, které je mají)), si shell zachovává svou vlastní představu o aktuálním pracovním adresáři. Je uloženo v $PWD
speciální proměnná.
Když to uděláte:
cd c/d
i když c
nebo c/d
jsou symbolické odkazy, zatímco $PWD
obsahuje /a/b
, připojí c/d
až do konce, takže $PWD
se změní na /a/b/c/d
. A když to uděláte:
cd ../e
Místo provádění chdir("../e")
, dělá to chdir("/a/b/c/e")
.
A pwd
příkaz vrátí pouze obsah $PWD
proměnná.
To je užitečné v interaktivních shellech, protože pwd
vypíše cestu k aktuálnímu adresáři, která poskytuje informace o tom, jak jste se tam dostali a pokud používáte pouze ..
v argumentech k cd
a ne jiné příkazy, je méně pravděpodobné, že vás to překvapí, protože cd a; cd ..
nebo cd a/..
obecně by vás vrátil tam, kde jste byli.
Nyní $PWD
se nezmění, pokud neuděláte cd
. Dokud příště nezavoláte cd
nebo pwd
, může se stát spousta věcí, kterákoli ze součástí $PWD
mohl být přejmenován. Aktuální adresář se nikdy nezmění (vždy je to stejný inode, i když může být smazán), ale jeho cesta ve stromu adresářů se může úplně změnit. getcwd()
vypočítá aktuální adresář pokaždé, když je zavolán, procházením stromu adresářů, takže jeho informace jsou vždy přesné, ale pro logický adresář implementovaný shelly POSIX jsou informace v $PWD
může zatuchnout. Takže po spuštění cd
nebo pwd
, některé shelly se proti tomu mohou chtít chránit.
V tomto konkrétním případě uvidíte různé chování s různými shelly.
Někteří jako ksh93
problém zcela ignorovat, takže vrátí nesprávné informace i po zavolání cd
(a neuvidíte chování, které vidíte u bash
tam).
Někteří mají rádi bash
nebo zsh
zkontrolujte, zda $PWD
je stále cesta k aktuálnímu adresáři na cd
, ale ne po pwd
.
pdksh kontroluje obě pwd
a cd
(ale po pwd
, neaktualizuje $PWD
)
ash
(alespoň ten, který se nachází v Debianu) nekontroluje, a když uděláte cd a
, ve skutečnosti to dělá cd "$PWD/a"
, takže pokud se aktuální adresář změnil a $PWD
již neukazuje na aktuální adresář, ve skutečnosti se nezmění na a
adresář v aktuálním adresáři, ale ten v $PWD
(a vrátí chybu, pokud neexistuje).
Pokud si s tím chcete hrát, můžete:
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)
v různých skořápkách.
Ve vašem případě, protože používáte bash
, za cd a
, bash
zkontroluje, že $PWD
stále ukazuje na aktuální adresář. K tomu volá stat()
na hodnotu $PWD
zkontrolovat jeho číslo inodu a porovnat ho s číslem .
.
Ale při vyhledávání $PWD
cesta zahrnuje vyřešení příliš mnoha symbolických odkazů, které stat()
vrátí s chybou, takže shell nemůže zkontrolovat, zda $PWD
stále odpovídá aktuálnímu adresáři, takže jej vypočítá znovu pomocí getcwd()
a aktualizace $PWD
podle toho.
Abychom nyní objasnili Patriceovu odpověď, kontrola počtu symbolických odkazů, na které narazíte při hledání cesty, slouží k ochraně před smyčkami symbolických odkazů. Nejjednodušší smyčku lze vytvořit pomocí
rm -f a b
ln -s a b
ln -s b a
Bez této ochrany, na cd a/x
, systém by musel najít místo a
odkazuje na, zjistí, že je to b
a je to symbolický odkaz, který odkazuje na a
a tak by to šlo donekonečna. Nejjednodušší způsob, jak se tomu bránit, je vzdát se po vyřešení více než libovolného počtu symbolických odkazů.
Nyní zpět do logického aktuálního pracovního adresáře a proč to není tak dobrá funkce. Je důležité si uvědomit, že je to pouze pro cd
v shellu a ne v jiných příkazech.
Například:
cd -- "$dir" && vi -- "$file"
není vždy stejné jako:
vi -- "$dir/$file"
Proto někdy zjistíte, že lidé doporučují vždy používat cd -P
ve skriptech, abyste předešli zmatkům (nechcete, aby váš software zpracovával argument ../x
odlišně od ostatních příkazů jen proto, že je napsán v shellu místo v jiném jazyce).
-P
možností je zakázat logický adresář zpracování tak cd -P -- "$var"
ve skutečnosti volá chdir()
na obsah $var
(alespoň tak dlouho jako $CDPATH
není nastaveno a kromě případů $var
je -
(nebo možná -2
, +3
… v některých skořápkách), ale to je jiný příběh). A po cd -P
, $PWD
bude obsahovat kanonickou cestu.