为什么在“#”中使用“-”!/ bin / sh-” shebang?


27
#!/ bin / sh-

经常(或至少曾经)是推荐的shebang使用来解释脚本的/bin/sh

为什么不直接#! /bin/sh#!/bin/sh

那是-为了什么

Answers:


37

这与您需要编写类似的原因:

rm -- *.txt

并不是

rm *.txt

除非可以保证.txt当前目录中的所有文件都没有以-。开头的名称。

在:

rm <arg>

<arg>如果以开头,则视为选项,-否则将其删除。在

rm- <arg>

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调用它的开头不会-(或+也是一个问题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 -具有更高的可移植性。特别是古代版本的shsh-选项终止日期和终止日期视为早getopt(),并且普遍使用--来长时间标记选项终止。Bourne shell(从70年代后期开始)解析其参数的方式,如果以开头,则仅考虑第一个参数作为选项-。之后的所有字符-将被视为选项名称;如果后面没有字符-,则没有选项。这样就卡住了,后来所有类似Bourne的Shell都被-视为标记选项结束的一种方式。

在Bourne外壳程序中(而不是在现代的Bourne外壳程序中),#! /bin/sh -euf该问题也可以解决,因为只考虑第一个参数作为选项。

现在,有人可以说我们在这里要学脚,这也是为什么我在上面的斜体字中写了这个需求

  1. 在他们的头脑中,没有人会以-或开头的脚本调用脚本,+或者将脚本放在以-或开头的目录中+
  2. 即使他们这样做了,第一个也会争辩说他们只能责备自己,而且,当您调用脚本时,通常更多的是来自shell或execvp()/ execlp()-type函数。在那种情况下,通常您the-script会根据要查找的内容来调用它们,$PATH在这种情况下,execve()系统调用的path参数通常以/(not -+)开头,或者./the-script好像您希望the-script在当前目录中运行(并且那么路径的开头./,而不是-也不+要么)。

现在,除了理论上的正确性问题外,还有另一个原因#! /bin/sh -被推荐为优良作法。这可以追溯到几个系统仍支持setuid脚本的时代。

如果您的脚本包含:

#! /bin/sh
/bin/echo "I'm running as root"

-r-sr-xr-x root bin在那些系统上,当该脚本由普通用户执行时,该脚本是setuid根目录(与许可一样)。

execve("/bin/sh", ["/bin/sh", "path/to/the-script"], [environ])

将完成root

如果用户创建了一个符号链接/tmp/-i -> path/to/the-script并以符号身份执行-i,它将以形式启动交互式shell(/bin/sh -iroot

-会的工作围绕(它不会解决的竞争条件的问题,或事实,一些sh像一些实现ksh88基于那些将会查找脚本参数不/$PATH虽然)。

如今,几乎所有系统都不再支持setuid脚本,而那些仍然支持setuid脚本(通常不是默认情况)的最终结果是execve("/bin/sh", ["/bin/sh", "/dev/fd/<n>", arg])<n>解决了此问题和比赛条件。

请注意,大多数解释器都会遇到类似的问题,而不仅仅是像Bourne这样的shell。非伯恩类外壳通常不支持-作为选项结束标记,但通常支持--(至少对于现代版本而言)。

#! /usr/bin/awk -f
#! /usr/bin/sed -f

没有问题作为下一个参数被认为是一个参数传递给-f在任何情况下的选择,但如果它仍然无法正常工作path/to/script-(在这种情况下,与大多数sed/ awk实现,sed/ awk不读从代码-文件,但来自stdin)。

还要注意,在大多数系统上,不能使用:

#! /usr/bin/env sh -
#! /usr/bin/perl -w --

与大多数系统一样,shebang机制在解释器路径之后仅允许一个参数。

至于是否使用#! /bin/sh -vs #!/bin/sh -,这只是个问题。我更喜欢前者,因为它可以使解释器路径更加可见,并使鼠标选择更加容易。有传说说在某些古老的Unix版本中需要该空间,但AFAIK尚未得到验证。

关于Unix shebang的很好的参考可以在https://www.in-ulm.de/~mascheck/various/shebang中找到。


1
这和#!/ bin / bash有什么关系吗?

1
@Joe是的,当然bash可以接受其他所有shell一样的选项。作为Bourne风格的POSIX外壳,bash接受#! /bin/bash -#! /bin/bash --
斯特凡Chazelas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.