帕特里斯(Patrice)在他的回答中指出了问题的根源,但是如果您想知道如何从那里得到解决的原因,那么这就是长话了。
您认为进程的当前工作目录并不复杂。它是进程的一个属性,是对目录类型文件的句柄,该目录的相对路径(在该进程进行的系统调用中)从此处开始。解析相对路径时,内核不需要知道当前目录的完整路径,它只需读取该目录文件中的目录条目即可找到相对路径的第一部分(与..
其他任何路径一样)文件),然后从那里继续。
现在,作为用户,您有时想知道该目录在目录树中的位置。对于大多数Unices,目录树是一棵没有循环的树。也就是说,从树的根(/
)到任何给定文件只有一条路径。该路径通常称为规范路径。
为了得到当前工作目录,什么程序需要做的仅仅是步行的路径(也下来,如果你想看到它的根树的底部)树回到根,找到节点的名称在途中。
例如,尝试查找其当前目录为的进程/a/b/c
将打开该..
目录(相对路径,..
当前目录中的条目也是如此),并查找与inode编号相同的目录类型的文件.
,找出c
匹配,然后打开../..
,依此类推,直到找到/
。那里没有歧义。
这就是getwd()
或getcwd()
C函数的作用,或者至少是过去所要做的。
在诸如现代Linux之类的某些系统上,有一个系统调用将规范路径返回到当前目录,该路径在内核空间中进行查找(并且即使您没有对所有组件的读取权限,也可以找到当前目录) ,那就是getcwd()
在那里。在现代Linux上,您还可以通过中的readlink()找到当前目录的路径/proc/self/cwd
。
这是大多数语言和早期Shell在将路径返回到当前目录时所做的事情。
你的情况,你可以调用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
将完全不同。特别是,..
in /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最终指定了这种行为,这意味着当今大多数shell也会这样做:
对于cd
和pwd
内置命令(并且仅适用于它们(尽管也适用于具有它们的shell上的popd
/ pushd
)),shell会维护自己对当前工作目录的想法。它存储在$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
or之前pwd
,可能会发生很多事情,的任何组件$PWD
都可以重命名。当前目录永不更改(尽管可以删除,但始终是相同的inode),但是其在目录树中的路径可以完全更改。getcwd()
每次通过走目录树来计算当前目录时,都会计算当前目录,因此其信息始终准确,但是对于POSIX Shell实现的逻辑目录,其中的信息$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
来检查其inode编号并将其与进行比较.
。
但是,当查找$PWD
路径涉及解析太多符号链接时,stat()
返回的错误将导致错误,因此Shell无法检查是否$PWD
仍与当前目录相对应,因此它再次使用进行计算getcwd()
并相应地进行更新$PWD
。
现在,为了阐明Patrice的答案,在查找路径时检查遇到的符号链接数是为了防止符号链接循环。最简单的循环可以用
rm -f a b
ln -s a b
ln -s b a
没有安全防护,在上cd a/x
,系统将不得不找到a
链接的位置,找到它的位置,b
并且是一个链接到的符号链接a
,并且该链接将无限期地继续下去。防止这种情况发生的最简单方法是在解决了多个符号链接后就放弃了。
现在回到逻辑当前工作目录,以及为什么它不是一个很好的功能。重要的是要意识到它仅适用cd
于shell,而不适用于其他命令。
例如:
cd -- "$dir" && vi -- "$file"
并不总是与以下内容相同:
vi -- "$dir/$file"
这就是为什么您有时会发现人们建议始终cd -P
在脚本中使用以避免混淆(您不希望您的软件../x
仅使用Shell而不是另一种语言来编写与其他命令不同的参数)。
该-P
选项是禁用的逻辑目录处理这样cd -P -- "$var"
实际上并调用chdir()
上的内容$var
(除非$var
是-
但那是另一回事)。并且在之后cd -P
,$PWD
将包含规范路径。