#! /bin/sh -
是(或至少是)经常推荐的舍邦让脚本由/bin/sh
(或#! /bin/bash -
bash、#! /bin/ksh -
ksh 等)解释。
为什么不只是#! /bin/sh
或#!/bin/sh
?
那个有什么-
用途?
答案1
这与您需要编写的原因类似:
rm -- *.txt
并不是
rm *.txt
除非你能保证.txt
当前目录中没有任何文件的名称以-
.
在:
R M<参数>
<arg>
如果它以文件开头-
,则被视为一个选项,否则被视为要删除的文件。在
R M - <参数>
<arg>
始终被视为要删除的文件,无论其是否开头-
。
这对于 来说也是一样的sh
。
当执行一个以
#! /bin/sh
通常与:
execve("path/to/the-script", ["the-script", "arg"], [environ])
系统将其转换为:
execve("/bin/sh", ["/bin/sh", "path/to/the-script", "arg"], [environ])
这path/to/the-script
通常不在脚本作者的控制之下。作者无法预测脚本副本将存储在何处,也无法预测以什么名称存储。特别是,他们不能保证path/to/the-script
调用它的 with 不会以 开头-
(或者+
这也是 的一个问题sh
)。这就是为什么我们需要此处-
标记选项的结束。
例如,在我的系统上zcat
(实际上像大多数其他脚本一样)是一个不遵循该建议的脚本示例:
$ head -n1 /bin/zcat
#!/bin/sh
$ mkdir +
$ ln -s /bin/zcat +/
$ +/zcat
/bin/sh: +/: invalid option
[...]
现在你可能会问为什么#! /bin/sh -
而不是#! /bin/sh --
?
虽然#! /bin/sh --
可以与 POSIX shell 一起使用,但#! /bin/sh -
更便携;特别是古代版本的sh
.sh
将-
as 和选项结束视为早于日期,getopt()
并且一般使用 来--
标记选项结束很长一段时间。 Bourne shell(从 70 年代末开始)解析其参数的方式,如果第一个参数以-
.之后的所有字符-
将被视为选项名称;如果 后面没有字符-
,则没有选项。这种情况被卡住了,后来所有类似 Bourne 的 shell 都将其视为-
标记选项结束的一种方式。
在 Bourne shell 中(但不是在现代的类似 Bourne shell 中),#! /bin/sh -eu
也可以解决这个问题,因为选项只考虑第一个参数。
现在,有人可能会说我们在这里很迂腐,这也是我写这篇文章的原因需要上面斜体字:
- 头脑正常的人不会调用以
-
或开头的脚本,或者将它们放在名称以或+
开头的目录中。-
+
- 即使他们这样做了,第一个人也会争辩说他们只能责怪自己,而且,当您调用脚本时,通常是来自 shell 或
execvp()
/execlp()
类型的函数。在这种情况下,通常您可以调用它们来the-script
查找它,在$PATH
这种情况下,系统调用的路径参数execve()
通常以/
(not-
或+
) 开头,或者就像./the-script
您希望the-script
在当前目录中运行一样(并且那么路径以./
,不是,-
也不+
是)开始。
现在除了理论之外正确性问题,还有另一个原因#! /bin/sh -
被推荐为好的做法。这可以追溯到几个系统仍然支持 setuid 脚本的时代。
如果您有一个脚本包含:
#! /bin/sh
/bin/echo "I'm running as root"
该脚本是 setuid root (与-r-sr-xr-x root bin
权限一样),在这些系统上,当由普通用户执行时,
execve("/bin/sh", ["/bin/sh", "path/to/the-script"], [environ])
将被完成root
!
如果用户创建了一个符号链接/tmp/-i -> path/to/the-script
并执行它-i
,那么它将启动一个交互式 shell( /bin/sh -i
) as root
。
可以-
解决这个问题(它不能解决竞争条件问题,或者某些sh
实现(例如基于 - 的实现)会在不带 in 的情况ksh88
下查找脚本参数)。/
$PATH
如今,几乎没有任何系统不再支持 setuid 脚本,并且其中一些仍然支持的系统(通常不是默认情况下)最终会执行一个execve("/bin/sh", ["/bin/sh", "/dev/fd/<n>", arg])
(<n>
在脚本上打开文件描述符以供读取的位置)来解决此问题和竞赛条件。
请注意,大多数解释器都会遇到类似的问题,而不仅仅是类似 Bourne 的 shell。非类似 Bourne 的 shell 通常不支持-
作为选项结束标记,但通常支持--
(至少对于现代版本)。
还
#! /usr/bin/awk -f
#! /usr/bin/sed -f
没有问题,因为在任何情况下下一个参数都被视为选项的参数,但如果是的-f
话它仍然不起作用(在这种情况下,对于大多数/实现,/不要从文件,但来自标准输入)。path/to/script
-
sed
awk
sed
awk
-
另请注意,在大多数系统上,无法使用:
#! /usr/bin/env sh -
#! /usr/bin/perl -w --
与大多数系统一样,shebang 机制只允许一解释器路径之后的参数。
至于是否使用#! /bin/sh -
vs #!/bin/sh -
,那只是一个品味问题。我更喜欢前者,因为它使解释器路径更明显并且使鼠标选择更容易。有一个传说某些古老的 Unix 版本需要这个空间但据我所知,这从未得到证实。
关于 Unix shebang 的非常好的参考可以在以下位置找到:https://www.in-ulm.de/~mascheck/various/shebang
1 尽管使用zsh
,您可以执行#! /bin/zsh -eu-
额外的操作-
,也标志着选项的结束。