POSIX shell 标准在此网站上说明
http://pubs.opengroup.org/onlinepubs/9699919799/
关于 shell 如何PATH
查找可执行文件:
“应从头到尾搜索列表,将文件名应用于每个前缀,直到找到具有指定名称和适当执行权限的可执行文件。”
嗯,这在真正的 POSIX 实现中并不是这样工作的:
man which
说:
“如果其参数是作为严格符合 POSIX 标准的 shell 中的命令给出的,则返回将在当前环境中执行的文件(或链接)的路径名。它通过在 PATH 中搜索与参数名称匹配的可执行文件来实现此目的。它不遵循符号链接。”
好的,我们来看看这种情况:
$ pwd /home/mark
$ echo $PATH /home/mark/bin:
...
$ ls -l bin/foobar
lrwxrwxrwx 1 mark mark 18 Dec 12 22:51 bin/foobar -> /home/mark/foobar1
$ touch foobar1
$ which foobar
$ chmod a+x foobar1
$ which foobar
/home/mark/bin/foobar
PATH
好的,这里有一个具有正确名称的符号链接,并且报告它ls
是可执行的。
which
根本不看它,而只对它指向的东西感兴趣。
尽管两者都man which
明确表示它不遵循符号链接(并且我们也确实看到它不遵循,因为which foobar
没有打印foobar1
),并且上面引用的 POSIX shell 文档也从未提及在PATH
算法中遵循符号链接。
那么,which
现有的 shell 是错误的,还是我没有理解文档?
澄清:
我知道并且可以解释现有的行为。我的问题不是“这是如何运作的?”。我知道。
我的问题是关于文档的:我引用的文档中哪里出错了。或者文档有误?
动机:我为什么关心?
好吧,我是一名实施者。不同的实施者有不同的要求。对我来说,要求是必须准确遵循当前 POSIX 标准的规定(或者更准确地说,尽可能地遵循,因为标准本身有些问题)。就像上帝的旨意一样。
现在,标准措辞非常清楚 - 没有提到跟踪符号链接,而在其他许多地方,需要这样做的地方都提到了。所以在这种情况下,不要这样做。
但是,我总是仔细检查它的行为方式dash
,bash
以确保万无一失。当然,这里也存在一个小问题,dash
尽管它被称为 POSIX,但有很多符合 POSIX 的小错误。 bash
,我还没有发现任何与 POSIX 相关的错误,但是…… bash 实际上不是 POSIX,它远不止于此。
就是这样。这就是我关心的原因。
答案1
符号链接本身的权限无关紧要。即使你尝试更改它们,也无法更改。
重要的是底层文件的权限。
PATH 中的目录包含指向可执行文件的符号链接是可以的。事实上,PATH 中的许多可执行文件很可能都是符号链接。例如,在类似 debian/ubuntu 的系统上:
$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Jan 23 2017 /bin/sh -> dash
文档
从man chmod
:
chmod 永远不会改变符号链接的权限;chmod 系统调用无法改变它们的权限。这不是问题,因为符号链接的权限从未被使用过。 但是,对于命令行上列出的每个符号链接,chmod 都会更改指向文件的权限。相反,chmod 会忽略在递归目录遍历期间遇到的符号链接。[重点补充。]
例子
shell 有一个测试,-x
用于确定文件是否可执行。让我们尝试一下:
$ ls -l
total 0
lrwxrwxrwx 1 john1024 john1024 7 Dec 12 23:36 foo -> foobar1
-rw-rw---- 1 john1024 john1024 0 Dec 12 23:36 foobar1
$ [ -x foo ] && echo foo is executable
$ chmod +x foobar1
$ [ -x foo ] && echo foo is executable
foo is executable
因此,就像您在 中发现的那样which
,除非底层文件可执行,否则 shell 不会将软链接视为可执行文件。
如何运作
在 Debian 系统上,which
是一个 shell 脚本。代码的相关部分是:
case $PROGRAM in
*/*)
if [ -f "$PROGRAM" ] && [ -x "$PROGRAM" ]; then
puts "$PROGRAM"
RET=0
fi
;;
*)
for ELEMENT in $PATH; do
if [ -z "$ELEMENT" ]; then
ELEMENT=.
fi
if [ -f "$ELEMENT/$PROGRAM" ] && [ -x "$ELEMENT/$PROGRAM" ]; then
puts "$ELEMENT/$PROGRAM"
RET=0
[ "$ALLMATCHES" -eq 1 ] || break
fi
done
;;
esac
如您所见,它使用-x
测试来确定文件是否可执行。
POSIX 指定-x
测试如下:
-x 路径名
如果路径名解析为文件的现有目录条目,并且该文件具有执行文件的权限,则为 True(或搜索它,如果它是目录)将被授予,如文件读取、写入和创建中所定义。如果无法解析路径名,或者如果路径名解析为文件的现有目录条目,则为 false,而该文件的执行(或搜索)权限将不被授予。[重点补充。]
因此,POSIX 检查路径名解决换句话说,它接受符号链接。
POSIX exec 函数
这POSIX exec 函数遵循符号链接。POSIX 规范详细地指定了当符号链接是循环的或太深时可能报告的错误情况,例如:
[ELOOP]
在解析路径或文件参数时遇到的符号链接中存在循环。
[ELOOP]
在解析路径或文件参数时遇到超过 {SYMLOOP_MAX} 个符号链接。
[ENAMETOOLONG]
由于在解析路径参数时遇到符号链接,替换的路径名字符串的长度超过了 {PATH_MAX}。
答案2
在这种情况下,符号链接被跟踪透明地,而不规范最终路径。换句话说,which
它不关心是否/home/mark/bin
是符号链接。它关心的是文件是否/home/mark/bin/foobar
存在。它不需要手动展平路径上的符号链接——操作系统可以自行完成这一操作。
确实,当which
询问文件信息时/home/mark/bin/foobar
,操作系统注意到它是一个符号链接,并跟踪它,并成功在目标目录中/home/mark/bin
找到。foobar
除非程序使用open(…, O_NOFOLLOW)
或fstatat(…, AT_SYMLINK_NOFOLLOW)
访问该文件,否则这是默认行为。
[评论已合并]
虽然你说 shell 实用程序会根据具体情况执行此操作,但内核系统调用并不相同:所有与文件相关的调用做默认跟随符号链接,除非给出“nofollow”标志。(即使 lstat 也会跟随除最后一个路径组件之外的所有路径组件中的符号链接。)
当规范没有明确提到如何处理符号链接时,它暗示将使用默认行为。也就是说,遵循路径算法的 shell 不会手动解析符号链接也不它是否明确选择不让操作系统执行相同的操作。(它只是将每个 $PATH 组件与可执行文件名称连接起来。)
当 which(1) 手册页说它不遵循符号链接时,它可能意味着几件事,但 GNU coreutils 版本是这样表述的:
当其中一个目录包含带有符号链接的路径时,它将认为两个等效目录不同。
这的范围要窄得多——它只意味着which
不会试图手动规范化所有路径以清除重复项,但这并不意味着该工具将选择退出操作系统的符号链接跟踪。例如,如果/bin
是符号链接/usr/bin
,则运行which -a sh
将返回两个都 /bin/sh
和/usr/bin/sh
。
答案3
shell 遵循其文档,因为它遵循路径名解析规则。 which
遵循其文档。 两者做的事情略有不同。
的输出which
是链接的文件名和路径,而不是符号链接指向的路径。这在手册页中有详细说明。
执行命令时,将按照第 4.13 节“路径名解析”“跟踪”链接在相同的执行文件的相关子句是:
在所有其他情况下,系统应在剩余路径名(如果有)前加上符号链接的内容作为前缀,但如果符号链接的内容为空字符串,则路径名解析将失败,函数将报告 [ENOENT] 错误,实用程序将写入等效的诊断消息,或者应使用包含符号链接的目录的路径名代替符号链接的内容。如果符号链接的内容仅由字符组成,则应从生成的组合路径名中省略剩余路径名的所有前导字符,只保留符号链接内容的前导字符。在出现前缀的情况下,如果组合长度超过 {PATH_MAX},并且实现认为这是一个错误,则路径名解析将失败,函数将报告 [ENAMETOOLONG] 错误,实用程序将写入等效的诊断消息。否则,解析的路径名应是刚刚创建的路径名的解析。如果生成的路径名不以 开头,则路径名的第一个文件名的前身将被视为包含符号链接的目录。