来自Shell 命令语言POSIX 规范页面:
如果 shell 命令文件的第一行以字符“#!”开头,则结果未指定。
#!
为什么POSIX 未指定行为?我发现令人困惑的是,如此便携且广泛使用的东西竟然有未指定的行为。
答案1
我想主要是因为:
实施之间的行为差异很大。看https://www.in-ulm.de/~mascheck/various/shebang/了解所有详细信息。
然而,它现在可以指定大多数类 Unix 实现的最小子集:例如
#! *[^ ]+( +[^ ]+)?\n
(在这一个或两个单词中仅包含可移植文件名字符集中的字符),其中第一个单词是本机可执行文件的绝对路径,但事实并非如此如果可执行文件是 setuid/setgid,并且实现定义了是否将解释器路径或脚本路径传递给argv[0]
解释器,则太长并且行为未指定。POSIX 无论如何都不指定可执行文件的路径。一些系统在
/bin
/中具有预 POSIX 实用程序/usr/bin
,并且在其他位置也有 POSIX 实用程序(例如在 Solaris 10 上,其中/bin/sh
有一个 Bourne shell,而 POSIX 实用程序位于 中/usr/xpg4/bin
;Solaris 11 将其替换为 ksh93,后者更符合 POSIX 标准,但大多数其他系统都将其替换为 ksh93)。中的工具/bin
仍然是古老的非 POSIX 工具)。有些系统不是 POSIX 系统,但具有 POSIX 模式/仿真。 POSIX 所需要的只是有一个记录的环境,在该环境中系统可以按 POSIX 方式运行。例如,请参见 Windows+Cygwin。实际上,对于 Windows+Cygwin,当脚本由 cygwin 应用程序而不是由本机 Windows 应用程序调用时,she-bang 就会受到尊重。
因此,即使 POSIX 指定了 shebang 机制,它也不能用于编写 POSIX
sh
//sed
...awk
脚本(另请注意,shebang 机制不能用于编写可靠的sed
/awk
脚本,因为它不允许传递结束选项标记)。
#!
现在,它未指定的事实并不意味着您不能使用它(好吧,它说如果您希望它只是一个常规注释而不是一个 she-bang,那么您不应该将第一行开头),但是如果您这样做,POSIX 不会给您任何保证。
根据我的经验,使用 shebangs 比使用 POSIX 编写 shell 脚本的方式更能保证可移植性:放弃 she-bang,用 POSIXsh
语法编写脚本,并希望调用该脚本的任何内容都会调用符合 POSIX 标准的脚本sh
,即如果您知道该脚本将在正确的环境中由正确的工具调用,则很好,否则就不行。
您可能需要执行以下操作:
#! /bin/sh -
if : ^ false; then : fine, POSIX system by default
else
# cover Solaris 10 or older. ": ^ false" returns false
# in the Bourne shell as ^ is an alias for | there for
# compatibility with the Thompson shell.
PATH=`getconf PATH`:$PATH; export PATH
exec /usr/xpg4/bin/sh - "$0" ${1+"$@"}
fi
# rest of script
如果您想移植到 Windows+Cygwin,您可能必须使用.bat
或.ps1
扩展名命名您的文件,并使用一些类似的技巧来cmd.exe
或在同一文件上powershell.exe
调用 cygwin 。sh
答案2
所有符合 POSIX 的 shell 的行为似乎都是一致的。我不认为这里需要回旋余地。
你看的还不够深入。
早在 20 世纪 80 年代,这个机制就被不是事实上的标准化。尽管丹尼斯·里奇(Dennis Ritchie)已经实现了它,但该实现尚未到达 AT&T 那边的公众手中。它实际上仅在 BSD 中公开可用和已知;具有 AT&T Unix 上不可用的可执行 shell 脚本。因此对其进行标准化是不合理的。当代 doco 就是这种情况的例证,它是众多此类 doco 之一:
注意BSD允许— 斯蒂芬·弗雷德 (1988)。 “在系统 X 版本 Y 上编程”。澳大利亚 Unix 系统用户组通讯。第 9 卷第 4 号。 111.#! interpreter
直接执行以a开头的文件,而SysV只允许直接执行a.out文件。这意味着 BSD 程序中的例程之一的实例exec…()
可能必须在 SysV 下进行更改才能执行/bin/sh
该程序的解释器(通常)。
这里重要的一点是,您正在查看 shell,而可执行 shell 脚本的存在实际上是函数的问题exec…()
。 shell 的作用包括前体可执行脚本机制的一部分,即使在今天,仍然可以在某些 shell 中找到(并且现在也强制要求exec…p()
函数子集),并且有些误导。标准在这方面需要解决的是exec…()
解释脚本如何工作以及 POSIX 最初创建的时间它从一开始就无法在目标操作系统的主要部分上运行。
一个次要问题是为什么此后没有标准化,特别是作为脚本解释器的幻数机制有到达了 AT&T 宇宙一侧的公众,并已exec…()
在系统5接口定义,到 20 世纪 90 年代之交:
解释器文件以以下形式的行开头—#!路径名 [参数]在哪里路径名是解释器的路径,并且精氨酸是一个可选参数。当您exec
使用解释器文件时,系统exec
将使用指定的解释器。
exec
。 System V 接口定义。第 1 卷,1991 年。
不幸的是,如今这种行为仍然与 20 世纪 80 年代一样大相径庭,并且没有真正共同的行为可以标准化。某些 Unices(例如著名的 HP-UX 和 FreeBSD)不支持脚本作为脚本的解释器。第一行是由空格分隔的一个、两个还是多个元素在 MacOS(以及 2005 年之前的 FreeBSD 版本)和其他操作系统之间有所不同。支持的最大路径长度各不相同。 ␀
POSIX 可移植文件名字符集之外的字符很棘手,前导空格和尾随空格也是如此。第 0 个、第 1 个和第 2 个参数最终的结果也很棘手,不同系统之间存在显着差异。有些目前符合 POSIX 标准,但非-Unix 系统仍然不支持任何此类机制,并且强制执行会将它们转换为不再符合 POSIX 标准。
进一步阅读
- 哪个 shell 解释器运行不带 shebang 的脚本?
- 在这种情况下,为什么我能够将参数传递给 /usr/bin/env ?
script
。 NetBSD 杂项信息手册。 2005年5月6日。- https://unix.stackexchange.com/a/605761/5132
答案3
正如其他一些答案所指出的,实施情况各不相同。这使得标准化变得困难和保持与现有脚本的向后兼容性。即使对于现代 POSIX 系统也是如此。例如,Linux 不会通过空格完全标记 shebang 行。 macOS 不允许脚本解释器是另一个脚本。