途中で階層を上がるパスでハマる

とある事情により特定のディレクトからしかファイルを読み込めない機能で、別のディレクトリから読み込みたいという要求が出てきたので../bar/data.txtのような指定をして誤魔化していたのだが、Windows環境からLinux環境に移してみるとうまく動かない。
ファイルはちゃんと置いてあるしディレクトリの区切り文字もスラッシュになっているし何がいけないのか。


その機能は内部でベースディレクトリのパスを連結してから処理をしていたのだが、そうして出来上がったパスの途中に存在しないディレクトリがあるのが原因だったようだ。

実際の構造
  /some/dir/bar/data.txt

その機能のベースディレクトリ
  /some/dir/foo

渡したパス
  ../bar/data.txt

その機能が構築したパス
  /some/dir/foo/../bar/data.txt

状況はこんな感じ。
/some/dir/fooは存在しないのだが、パスを正規化すれば/some/dir/bar/data.txtとなるし、そのファイルは実在する。
これでWindowsは動いてくれるのだがLinuxでは動いてくれない。
どうやら途中に存在しないパスがあってはダメらしい。

何故ダメなのか

はっきりしたことはわからないのだが、どうやらシンボリックリンクが存在するかもしれないので論理的な正規化はできないということらしい。
つまり/some/dir/fooが/another/dir/hogeへのシンボリックリンクだったとしたら、/some/dir/fooの時点で/another/dir/hogeになって、そこから一つ上がったら/another/dirであって、/some/dirではないということ。


そしてそうなるかどうかはパスの文字列だけじゃわからなくて実際にアクセスしてみないとわからないわけだが、実際にアクセスしようとしたら存在しなかったのでエラーになった、ということらしい。
(2013-20-21追記) 但し、cat ../../../../../../../etc/passwdみたいなルートの上まで遡ろうとするやつはエラーにはならない。


ただ、cdでカレントディレクトリを変更する場合はシンボリックの先に飛んで上に戻ると飛ぶ前に戻ったりもするのでよくわからない。ディレクトリ移動は別なのかもしれない。

cd -Pとpwd -P (2013-10-21追加)

cdとpwdには-Pと-Lというオプションがあって、それぞれシンボリックリンクを解決するかしないかを指定することができる。

$ mkdir /tmp/foo
$ ln -s /tmp/foo /tmp/bar

# 通常はシンボリックリンクである/tmp/barになる
$ cd /tmp/bar
$ pwd
/tmp/bar

# -Pを指定すると解決先の/tmp/fooになる
$ cd -P /tmp/bar
$ pwd
/tmp/foo

# pwdも同じ
$ cd /tmp/bar
$ pwd
/tmp/bar
$ pwd -P
/tmp/foo

# よくある(?)使い方:今いる場所のリンクじゃないパスに移動
$ cd /tmp/bar
$ pwd
/tmp/bar
$ cd -P .    # ←これ
$ pwd
/tmp/foo

これらのデフォルトの挙動は、bashならset -o physical、zshならsetopt chase_linksで変更ができる。