我编写了一个小 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
这里发生了什么?
答案1
帕特里斯确定了问题的根源他的回答,但如果你想知道如何从那里到达为什么会得到这个,这是一个很长的故事。
进程的当前工作目录并不复杂。它是进程的一个属性,是相对路径(在进程进行的系统调用中)开始的目录类型文件的句柄。解析相对路径时,内核不需要知道当前目录的完整路径,它只需读取该目录文件中的目录条目以查找相对路径的第一个组成部分(并且与..
任何其他路径一样)文件)并从那里继续。
现在,作为用户,您有时想知道该目录位于目录树中的位置。对于大多数 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
or/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 最终指定了这种行为,这意味着现在大多数 shell 也这样做:
对于cd
和pwd
内置命令 (并且只为他们(尽管也适用于具有它们的 shell 上的popd
/ pushd
)),shell 维护自己对当前工作目录的想法。它存储在$PWD
特殊变量中。
当你这样做时:
cd c/d
即使c
或c/d
是符号链接,当$PWD
contains时/a/b
,它会附加c/d
到末尾,因此$PWD
变为/a/b/c/d
。当你这样做时:
cd ../e
它不是做chdir("../e")
,而是做chdir("/a/b/c/e")
。
并且该pwd
命令仅返回变量的内容$PWD
。
这在交互式 shell 中很有用,因为pwd
输出当前目录的路径,其中提供了有关如何到达那里的信息,并且只要您只..
在参数中使用cd
而不是其他命令,它就不太可能让您感到惊讶,因为cd a; cd ..
orcd a/..
通常会让您返回到你所在的地方。
现在,$PWD
除非您执行cd
.在您下次调用cd
或之前pwd
,可能会发生很多事情, 的任何组件$PWD
都可以被重命名。当前目录永远不会改变(它总是相同的索引节点,尽管它可以被删除),但它在目录树中的路径可能会完全改变。getcwd()
每次调用时都会通过遍历目录树来计算当前目录,因此其信息始终准确,但对于 POSIX shell 实现的逻辑目录,其中的信息$PWD
可能会变得过时。因此,在运行cd
或 时pwd
,某些 shell 可能想要防止这种情况。
在该特定实例中,您会看到不同 shell 的不同行为。
有些人喜欢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
在您的情况下,由于您在a 之后使用cd a
,bash
检查它$PWD
是否仍然指向当前目录。为此,它调用stat()
的值$PWD
来检查其索引节点号并将其与 的值进行比较.
。
但是,当查找路径$PWD
涉及解析太多符号链接时,会stat()
返回错误,因此 shell 无法检查是否$PWD
仍然对应于当前目录,因此它会再次计算getcwd()
并相应更新$PWD
。
现在,为了澄清帕特里斯的答案,在查找路径时检查遇到的符号链接数量是为了防止符号链接循环。最简单的循环可以用
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"
这就是为什么您有时会发现人们建议始终在脚本中使用以避免混淆(您不希望您的软件仅仅因为它是用 shell 而不是其他语言编写的而cd -P
处理与其他命令不同的参数)。../x
该-P
选项是禁用逻辑目录处理 socd -P -- "$var"
实际上确实调用了chdir()
内容$var
(至少只要$CDPATH
它没有设置,并且除了 when $var
is -
(或者可能-2
,+3
...在某些 shell 中),但那是另一个故事)。在cd -P
,之后$PWD
将包含规范路径。
答案2
这是 Linux 内核源代码中硬编码限制的结果;为了防止拒绝服务,嵌套符号链接的数量限制为 40(可在follow_link()
功能inside fs/namei.c
,由nested_symlink()
内核源代码调用)。
对于支持符号链接的其他内核,您可能会得到类似的行为(可能还有 40 以外的限制)。