同じディレクトリを指すシンボリックリンクをたどり続けるとどうなるかを確認するために、小さな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
ここで何が起こっているのですか?
承認された回答:
パトリスは彼の答えで問題の原因を特定しましたが、そこからどのようにそれを得るのかを知りたい場合は、ここに長い話があります。
プロセスの現在の作業ディレクトリは、複雑すぎるとは思わないでしょう。これは、相対パス(プロセスによって行われるシステムコール内)が開始するディレクトリタイプのファイルへのハンドルであるプロセスの属性です。相対パスを解決する場合、カーネルは(a)現在のディレクトリへのフルパスを知る必要はなく、そのディレクトリファイル内のディレクトリエントリを読み取って、相対パス(および.. その点では他のファイルと同じです)そしてそこから続きます。
ここで、ユーザーとして、そのディレクトリがディレクトリツリーのどこにあるかを知りたい場合があります。ほとんどのUnicesでは、ディレクトリツリーはループのないツリーです。つまり、ツリーのルートからのパスは1つだけです(/ )任意のファイルに。そのパスは一般に正規パスと呼ばれます。
現在の作業ディレクトリのパスを取得するには、プロセスが実行する必要があるのは、上に移動するだけです(下に移動 ルートが下部にあるツリーを表示したい場合は、ツリーをルートに戻し、途中でノードの名前を見つけます。
たとえば、現在のディレクトリが/a/b/cであることを確認しようとするプロセス 、..を開きます ディレクトリ(相対パス、つまり.. は現在のディレクトリのエントリです)、. 、その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シェルでは、次の場合:
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/.. 通常、元の場所に戻ります。
さて、$PWD cdを実行しない限り、変更されません 。次回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をチェックします まだ現在のディレクトリを指しています。そのために、stat()を呼び出します。 $PWDの値について 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"を処理します 実際にはchdir()を呼び出します $varのコンテンツについて (少なくとも$CDPATHである限り 設定されていません。ただし、$varの場合を除きます。 -です (またはおそらく-2 、+3 …一部のシェルでは)しかし、それは別の話です)。そしてcd -Pの後 、$PWD 正規のパスが含まれます。