为什么“#! /bin/sh -” shebang 中的“-”?

为什么“#! /bin/sh -” shebang 中的“-”?
#! /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也可以解决这个问题,因为选项只考虑第一个参数。

现在,有人可能会说我们在这里很迂腐,这也是我写这篇文章的原因需要上面斜体字:

  1. 头脑正常的人不会调用以-或开头的脚本,或者将它们放在名称以或+开头的目录中。-+
  2. 即使他们这样做了,第一个人也会争辩说他们只能责怪自己,而且,当您调用脚本时,通常是来自 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-sedawksedawk-

另请注意,在大多数系统上,无法使用:

#! /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-额外的操作-,也标志着选项的结束。

相关内容