同じディレクトリを指すシンボリックリンクをたどっているときに何が起こるかを確認するために、小さなbashスクリプトを作成しました。非常に長い作業ディレクトリを作成するか、クラッシュすることを期待していました。しかし、その結果は私を驚かせました...
mkdir a
cd a
ln -s ./. a
for i in `seq 1 1000`
do
cd a
pwd
done
出力の一部は
${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
ここで何が起きてるの?
Patriceは 彼の答え で問題の原因を特定しましたが、そこからどのようにしてそれを得るのかを知りたいのであれば、ここに長い話があります。
プロセスの現在の作業ディレクトリは、複雑すぎるとは思わないでしょう。これは、プロセスの属性であり、相対パス(プロセスによって行われるシステムコール内)が開始するディレクトリタイプのファイルへのハンドルです。相対パスを解決する場合、カーネルは(a)現在のディレクトリへのフルパスを知る必要はありません。相対パスの最初のコンポーネントを見つけるために、そのディレクトリファイルのディレクトリエントリを読み取るだけです(そして..
はその点で他のファイル)とそこから続きます。
ここで、ユーザーとして、ディレクトリがディレクトリツリーのどこにあるかを知りたい場合があります。ほとんどのUnicesでは、ディレクトリツリーはループのないツリーです。つまり、ツリーのルート(/
)から任意のファイルへのパスは1つだけです。そのパスは一般に正規パスと呼ばれます。
現在の作業ディレクトリのパスを取得するために、プロセスが実行する必要があるのは、(ルートが一番下にあるツリーを表示したい場合は、downだけ)ルートにツリーを戻し、途中のノードの名前を見つけます。
たとえば、現在のディレクトリが/a/b/c
であることを検出しようとするプロセスは、..
ディレクトリ(相対パスなので、..
は現在のディレクトリのエントリです)を開き、.
と同じiノード番号を持つタイプのディレクトリのファイルを探します、c
が一致することを確認し、../..
が見つかるまで/
を開きます。あいまいさはありません。
これは、getwd()
またはgetcwd()
C関数が行うこと、または少なくとも以前は使用していたことです。
最新のLinuxのような一部のシステムでは、カーネル空間でそのルックアップを実行する現在のディレクトリへの正規パスを返すシステムコールがあります(すべてのコンポーネントへの読み取りアクセス権がない場合でも、現在のディレクトリを見つけることができます)。 、それがgetcwd()
が呼び出すものです。最近のLinuxでは、/proc/self/cwd
のreadlink()を使用して現在のディレクトリへのパスを見つけることもできます。
これが、現在のディレクトリへのパスを返すときに、ほとんどの言語と初期のシェルが行うことです。
あなたの場合、好きなときにcd a
を呼び出すことができます。これは.
へのシンボリックリンクであるため、現在のディレクトリは変更されないため、getcwd()
、pwd -P
、python -c 'import os; print os.getcwd()'
、Perl -MPOSIX -le 'print getcwd'
のすべてが${HOME}
を返します。
今、シンボリックリンクはそれらすべてを複雑にしました。
symlinks
は、ディレクトリツリー内のジャンプを許可します。 /a/b/c
では、/a
または/a/b
または/a/b/c
がシンボリックリンクである場合、/a/b/c
の正規パスは完全に異なるものになります。特に、..
の/a/b/c
エントリは必ずしも/a/b
ではありません。
Bourne Shellで、次の場合:
cd /a/b/c
cd ..
あるいは:
cd /a/b/c/..
最終的に/a/b
になる保証はありません。
と同じように:
vi /a/b/c/../d
必ずしも以下と同じではありません。
vi /a/b/d
ksh
は、論理的な現在の作業ディレクトリの概念を導入して、なんとかしてそれを回避しました。人々はそれに慣れ、POSIXはその振る舞いを指定することになりました。つまり、最近のほとんどのシェルも同様にそれを行います。
cd
およびpwd
組み込みコマンド(およびそれらのみ(ただし、それらを含むシェルではpopd
/pushd
)の場合)、シェルは独自のコマンドを保持します現在の作業ディレクトリのアイデア。これは、$PWD
特殊変数に格納されます。
あなたがするとき:
cd c/d
c
またはc/d
がシンボリックリンクであっても、$PWD
に/a/b
が含まれている場合、c/d
が末尾に追加されるため、$PWD
は/a/b/c/d
になります。そしてあなたがするとき:
cd ../e
chdir("../e")
を実行する代わりに、chdir("/a/b/c/e")
を実行します。
また、pwd
コマンドは、$PWD
変数の内容のみを返します。
pwd
は現在のディレクトリへのパスを出力するので、cd
の引数に..
のみを使用し、他のコマンドは使用しない限り、驚くほどその可能性は低くなります。cd a; cd ..
またはcd a/..
通常、あなたはあなたがいた場所に戻ります。
これで、cd
を実行しない限り、$PWD
は変更されません。次回cd
またはpwd
を呼び出すまで、多くのことが発生し、$PWD
のコンポーネントの名前が変更される可能性があります。現在のディレクトリは変更されません(削除される可能性はありますが、常に同じiノードです)が、ディレクトリツリー内のパスは完全に変更される可能性があります。 getcwd()
は、ディレクトリツリーをたどって呼び出されるたびに現在のディレクトリを計算するため、その情報は常に正確ですが、POSIXシェルによって実装される論理ディレクトリの場合、$PWD
の情報が古くなる可能性があります。したがって、cd
またはpwd
を実行すると、一部のシェルはそれを防ぐことができます。
その特定のインスタンスでは、さまざまなシェルでさまざまな動作が見られます。
ksh93
のようなものは問題を完全に無視するので、cd
を呼び出した後でも誤った情報を返します(そして、bash
で見られるような動作はありません)。
bash
やzsh
などの一部は、$PWD
がcd
ではなく、pwd
ではなく、現在のディレクトリへのパスであることを確認します。
pdkshはpwd
とcd
の両方をチェックします(ただし、pwd
は$PWD
を更新しません)
ash
(少なくともDebianで見つかったもの)はチェックせず、cd a
を実行すると実際にcd "$PWD/a"
を実行するため、現在のディレクトリが変更され、$PWD
が現在のディレクトリを指していなければ、実際にはa
に変更されません。現在のディレクトリのディレクトリですが、$PWD
のディレクトリです(存在しない場合はエラーを返します)。
あなたがそれで遊びたいのなら、あなたはすることができます:
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)
さまざまなシェルで。
あなたの場合、bash
を使用しているので、cd a
の後、bash
は$PWD
が現在のディレクトリを指していることを確認します。そのためには、$PWD
の値に対してstat()
を呼び出して、そのiノード番号を確認し、.
の値と比較します。
しかし、$PWD
パスの検索に含まれるシンボリックリンクの解決が多すぎる場合、そのstat()
はエラーで返されるため、シェルは$PWD
が現在のディレクトリにまだ対応しているかどうかを確認できないため、getcwd()
および$PWD
を適宜更新します。
ここで、Patriceの答えを明確にするために、パスの検索中に遭遇したシンボリックリンクの数のチェックは、シンボリックリンクのループを防ぐことです。最も単純なループは
rm -f a b
ln -s a b
ln -s b a
その安全な保護がなければ、cd a/x
に基づいて、システムはa
がリンクしている場所を見つける必要があり、b
であり、a
にリンクしているシンボリックリンクであることがわかります。これを防ぐ最も簡単な方法は、任意の数を超えるシンボリックリンクを解決した後で中止することです。
論理的な現在の作業ディレクトリに戻り、なぜそれがそれほど優れた機能ではないのかを説明します。シェルのcd
専用であり、他のコマンドではないことを理解することが重要です。
例えば:
cd -- "$dir" && vi -- "$file"
常に同じではありません:
vi -- "$dir/$file"
そのため、混乱を避けるために、スクリプトでcd -P
を常に使用することをお勧めする場合があります(別の言語ではなくシェルで記述されているという理由だけで、ソフトウェアで../x
の引数を他のコマンドとは異なる方法で処理する必要はありません)。
-P
オプションは論理ディレクトリの処理を無効にするため、cd -P -- "$var"
は実際に$var
のコンテンツに対してchdir()
を呼び出します(ただし、$var
が-
である場合は別です)。また、cd -P
の後には、$PWD
に正規パスが含まれます。
これは、Linuxカーネルソースにハードコードされた制限が原因です。サービス拒否を防ぐために、ネストされたシンボリックリンクの数の制限は40です(カーネルのfollow_link()
によって呼び出されるfs/namei.c
内の nested_symlink()
関数 にあります)ソース)。
シンボリックリンクをサポートする他のカーネルでも、おそらく同様の動作(おそらく40以外の制限)が発生します。