J'ai écrit un petit script bash pour voir ce qui se passe lorsque je continue à suivre un lien symbolique qui pointe vers le même répertoire. Je m'attendais à ce qu'il crée un très long répertoire de travail ou qu'il plante. Mais le résultat m'a surpris…
mkdir a
cd a
ln -s ./. a
for i in `seq 1 1000`
do
cd a
pwd
done
Une partie de la sortie est
${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
que se passe-t-il ici ?
Réponse acceptée :
Patrice a identifié la source du problème dans sa réponse, mais si vous voulez savoir comment aller de là et pourquoi vous obtenez cela, voici la longue histoire.
Le répertoire de travail actuel d'un processus n'est rien que vous penseriez trop compliqué. C'est un attribut du processus qui est un handle vers un fichier de type répertoire d'où partent les chemins relatifs (dans les appels système effectués par le processus). Lors de la résolution d'un chemin relatif, le noyau n'a pas besoin de connaître le chemin complet (a) vers ce répertoire courant, il lit simplement les entrées de répertoire dans ce fichier de répertoire pour trouver le premier composant du chemin relatif (et ..
est comme n'importe quel autre fichier à cet égard) et continue à partir de là.
Maintenant, en tant qu'utilisateur, vous aimez parfois savoir où se trouve ce répertoire dans l'arborescence des répertoires. Avec la plupart des Unix, l'arborescence des répertoires est une arborescence, sans boucle. Autrement dit, il n'y a qu'un seul chemin depuis la racine de l'arbre (/
) à un fichier donné. Ce chemin est généralement appelé chemin canonique.
Pour obtenir le chemin du répertoire de travail actuel, ce qu'un processus doit faire est simplement de monter (enfin descendre si vous aimez voir un arbre avec sa racine en bas) l'arbre revient à la racine, en trouvant les noms des nœuds sur le chemin.
Par exemple, un processus essayant de découvrir que son répertoire courant est /a/b/c
, ouvrirait le ..
répertoire (chemin relatif, donc ..
est l'entrée dans le répertoire courant) et recherchez un fichier de type répertoire avec le même numéro d'inode que .
, découvrez que c
correspond, puis ouvre ../..
et ainsi de suite jusqu'à ce qu'il trouve /
. Il n'y a pas d'ambiguïté.
C'est ce que le getwd()
ou getcwd()
Les fonctions C font ou du moins faisaient.
Sur certains systèmes comme Linux moderne, il existe un appel système pour renvoyer le chemin canonique vers le répertoire courant qui effectue cette recherche dans l'espace noyau (et vous permet de trouver votre répertoire courant même si vous n'avez pas accès en lecture à tous ses composants) , et c'est ce que getcwd()
appelle là-bas. Sur Linux moderne, vous pouvez également trouver le chemin vers le répertoire courant via un readlink() sur /proc/self/cwd
.
C'est ce que font la plupart des langages et des premiers shells lorsqu'ils retournent le chemin vers le répertoire courant.
Dans votre cas, vous pouvez appeler cd a
autant de fois que vous le souhaitez, car il s'agit d'un lien symbolique vers .
, le répertoire courant ne change pas donc tout getcwd()
, pwd -P
, python -c 'import os; print os.getcwd()'
, perl -MPOSIX -le 'print getcwd'
renverrait votre ${HOME}
.
Maintenant, les liens symboliques ont compliqué tout ça.
symlinks
autoriser les sauts dans l'arborescence des répertoires. Dans /a/b/c
, si /a
ou /a/b
ou /a/b/c
est un lien symbolique, alors le chemin canonique de /a/b/c
serait quelque chose de complètement différent. En particulier, le ..
entrée dans /a/b/c
n'est pas nécessairement /a/b
.
Dans le shell Bourne, si vous le faites :
cd /a/b/c
cd ..
Ou encore :
cd /a/b/c/..
Il n'y a aucune garantie que vous vous retrouverez dans /a/b
.
Comme :
vi /a/b/c/../d
n'est pas nécessairement la même chose que :
vi /a/b/d
ksh
introduit un concept de répertoire de travail courant logique pour contourner cela d'une manière ou d'une autre. Les gens s'y sont habitués et POSIX a fini par spécifier ce comportement, ce qui signifie que la plupart des shells le font également :
Pour le cd
et pwd
commandes intégrées (et uniquement pour elles (mais aussi pour popd
/pushd
sur les shells qui en ont)), le shell conserve sa propre idée du répertoire de travail courant. Il est stocké dans le $PWD
variable spéciale.
Lorsque vous faites :
cd c/d
même si c
ou c/d
sont des liens symboliques, tandis que $PWD
contient /a/b
, il ajoute c/d
à la fin donc $PWD
devient /a/b/c/d
. Et quand vous le faites :
cd ../e
Au lieu de faire chdir("../e")
, il fait chdir("/a/b/c/e")
.
Et le pwd
la commande ne renvoie que le contenu du $PWD
variables.
C'est utile dans les shells interactifs car pwd
affiche un chemin vers le répertoire courant qui donne des informations sur la façon dont vous y êtes arrivé et tant que vous n'utilisez que ..
en arguments de cd
et pas d'autres commandes, il est moins susceptible de vous surprendre, car cd a; cd ..
ou cd a/..
vous ramènerait généralement là où vous étiez.
Maintenant, $PWD
n'est pas modifié sauf si vous faites un cd
. Jusqu'à la prochaine fois que vous appelez cd
ou pwd
, beaucoup de choses peuvent arriver, n'importe lequel des composants de $PWD
pourrait être renommé. Le répertoire courant ne change jamais (c'est toujours le même inode, bien qu'il puisse être supprimé), mais son chemin dans l'arborescence des répertoires peut changer complètement. getcwd()
calcule le répertoire courant chaque fois qu'il est appelé en parcourant l'arborescence des répertoires afin que ses informations soient toujours exactes, mais pour le répertoire logique implémenté par les shells POSIX, les informations dans $PWD
pourrait devenir obsolète. Donc, lors de l'exécution de cd
ou pwd
, certains obus voudront peut-être s'en protéger.
Dans ce cas particulier, vous voyez différents comportements avec différents shells.
Certains comme ksh93
ignorez complètement le problème, il renverra donc des informations incorrectes même après avoir appelé cd
(et vous ne verriez pas le comportement que vous voyez avec bash
là).
Certains comme bash
ou zsh
vérifiez que $PWD
est toujours un chemin vers le répertoire courant sur cd
, mais pas sur pwd
.
pdksh vérifie à la fois pwd
et cd
(mais sur pwd
, ne met pas à jour $PWD
)
ash
(du moins celui trouvé sur Debian) ne vérifie pas, et quand vous faites cd a
, il fait en fait cd "$PWD/a"
, donc si le répertoire courant a changé et $PWD
ne pointe plus vers le répertoire courant, il ne changera en fait pas le a
répertoire dans le répertoire courant, mais celui dans $PWD
(et renvoie une erreur si elle n'existe pas).
Si vous voulez jouer avec, vous pouvez faire :
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)
dans divers coquillages.
Dans votre cas, puisque vous utilisez bash
, après un cd a
, bash
vérifie que $PWD
pointe toujours vers le répertoire courant. Pour ce faire, il appelle stat()
sur la valeur de $PWD
pour vérifier son numéro d'inode et le comparer avec celui de .
.
Mais lorsque la recherche du $PWD
path implique la résolution de trop de liens symboliques, que stat()
retourne avec une erreur, donc le shell ne peut pas vérifier si $PWD
correspond toujours au répertoire courant, il le recalcule donc avec getcwd()
et met à jour $PWD
en conséquence.
Maintenant, pour clarifier la réponse de Patrice, cette vérification du nombre de liens symboliques rencontrés lors de la recherche d'un chemin consiste à se prémunir contre les boucles de liens symboliques. La boucle la plus simple peut être faite avec
rm -f a b
ln -s a b
ln -s b a
Sans ce garde-fou, sur un cd a/x
, le système devrait trouver où a
liens vers, trouve que c'est b
et est un lien symbolique qui renvoie à a
, et cela durerait indéfiniment. Le moyen le plus simple de s'en prémunir est d'abandonner après avoir résolu plus qu'un nombre arbitraire de liens symboliques.
Revenons maintenant au répertoire de travail courant logique et pourquoi ce n'est pas une si bonne fonctionnalité. Il est important de réaliser que c'est uniquement pour cd
dans le shell et non dans d'autres commandes.
Par exemple :
cd -- "$dir" && vi -- "$file"
n'est pas toujours le même que :
vi -- "$dir/$file"
C'est pourquoi vous constaterez parfois que les gens recommandent de toujours utiliser cd -P
dans les scripts pour éviter toute confusion (vous ne voulez pas que votre logiciel gère un argument de ../x
différemment des autres commandes simplement parce qu'il est écrit en shell au lieu d'un autre langage).
Le -P
l'option est de désactiver le répertoire logique gérer donc cd -P -- "$var"
appelle en fait chdir()
sur le contenu de $var
(au moins tant que $CDPATH
il n'est pas défini, et sauf lorsque $var
est -
(ou éventuellement -2
, +3
… dans certains coquillages) mais c'est une autre histoire). Et après un cd -P
, $PWD
contiendra un chemin canonique.