POSIX为什么未指定`#!`语法的行为?


17

在POSIX规范的Shell命令语言页面中:

如果shell命令文件的第一行以字符“#!”开头,则结果不确定。

为什么#!POSIX无法指定行为?我感到困惑的是,如此便携式和广泛使用的东西将具有未指定的行为。


1
标准保留了未指定的内容,不会将实现与特定行为捆绑在一起。例如,“登录”是“用户通过其获得对系统的访问权的未指定活动”。
库萨兰达

2
由于POSIX没有指定可执行路径,因此shebang行本质上是不可移植的。我不确定通过指定它会获得多少收益。
迈克尔·荷马

1
@MichaelHomer,肯定不是吗?该标准可以指定该行包含用于解释程序的路径,即使不告诉该路径应该是什么。
ilkkachu

1
@HaroldFischer除非它不是由外壳程序解释的,否则它是由OS内核(至少在Linux上完成的,它实际上可以在构建期间禁用此支持)或任何实现该exec()功能的库解释的。因此,检查多个外壳并不能真正告诉您它的可移植性。
奥斯汀·海默加恩

2
@HaroldFischer此外,即使在兼容POSIX的操作系统中,行为也不一致。Linux和macOS的行为有所不同:Linux并未完全用空格标记出shebang行。macOS不允许脚本解释器为另一个脚本。另请参见en.wikipedia.org/wiki/Shebang_(Unix)#Portability
jamesdlin

Answers:


21

我认为主要是因为:

  • 实施之间的行为差​​异很大。有关所有详细信息,请参见https://www.in-ulm.de/~mascheck/various/shebang/

    但是,它现在可以指定大多数类似Unix的实现的最小子集:例如#! *[^ ]+( +[^ ]+)?\n(只有可移植文件名字符集中的字符位于这两个词中),其中第一个词是本机可执行文件的绝对路径,而事实并非如此时间太长,如果可执行文件是setuid / setgid,则行为未指定,实现定义了将解释器路径还是脚本路径传递argv[0]给了解释器。

  • POSIX始终不指定可执行文件的路径。几个系统在/bin/中/usr/bin具有POSIX 之前的实用程序,而在其他地方也具有POSIX实用程序(例如在Solaris 10上/bin/sh是Bourne shell,而POSIX则在其中/usr/xpg4/bin; Solaris 11用ksh93替换了它,它更符合POSIX,但其他大多数工具/bin仍然是古老的非POSIX 工具)。某些系统不是POSIX的,但是具有POSIX模式/仿真。POSIX所要求的只是在一个有文档记录的环境中,系统在该环境中表现为POSIXly。

    例如,参见Windows + Cygwin。实际上,在Windows + Cygwin中,当由cygwin应用程序而不是由本机Windows应用程序调用脚本时,会放任自流。

    因此,即使POSIX指定它不能被用来写家当机制POSIX sh/ sed/ awk...脚本(也注意到家当机制不能被用来编写可靠sed/ awk脚本,因为它不允许传递一个最终的选项标记)。

现在,未指定它的事实并不意味着您无法使用它(嗯,它表示#!如果您希望它只是常规注释而不是爆炸的话,则不应以第一行开头),但是POSIX不能保证您这样做。

以我的经验,使用shebangs比使用POSIX编写Shell脚本的方式为您提供了更多的可移植性保证:不用担心,用POSIX sh语法编写脚本,并希望任何调用脚本的人都可以调用sh与之兼容的POSIX 。如果您知道脚本将在正确的环境中由正确的工具调用,则可以,否则可以。

您可能需要执行以下操作:

#! /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 Thomson 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


有趣的是,从第5期开始:“构造#!保留给希望提供该扩展的实现。便携式应用程序不能将#!用作Shell脚本的第一行;它可能不会解释为注释。”
muru

@muru如果脚本是真正可移植的,则在运行POSIX的真正POSIX系统上sh,它不需要hashbang行,因为它将由POSIX执行sh
库萨兰达

1
@Kusalananda仅当使用了execlp或时execvp才是对的,对吗?如果使用execve,会导致ENOEXEC吗?
muru

9

在所有POSIX投诉外壳之间,行为似乎是一致的。我认为这里不需要摆动空间。

您看起来不够深入。

早在1980年代,这种机制实际上还没有标准化。尽管丹尼斯·里奇(Dennis Ritchie)实施了该实现,但该实现尚未在宇宙AT&T方面向公众公开。实际上,它仅在BSD中公开可用并已知;带有在AT&T Unix上不可用的可执行Shell脚本。因此,对其进行标准化是不合理的。这种当代的doco就是事态的例子,其中之一是:

请注意,BSD允许以开始的文件#! interpreter直接执行,而SysV只允许a.out的文件直接执行。这意味着exec…()可能必须在SysV下更改BSD程序中例程之一的实例,以/bin/sh代替执行该程序的解释器(通常是)。
斯蒂芬·弗雷德(1988)。“在系统X版本Y上编程”。澳大利亚Unix系统用户组通讯。第9卷第4期。111。

这里重要的一点是您正在查看shell,而可执行shell脚本的存在实际上是exec…()功能的问题。Shell所做的事情包括可执行脚本机制的前身,即使在今天(以及今天,对于exec…p()功能子集的要求)中,仍然可以在某些Shell中找到它们,并且有些误导。在这方面,标准需要解决的是如何exec…()在解释的脚本上工作,而在最初创建POSIX时,它根本就没有在目标操作系统的主要部分起作用

一个从属问题是为什么自从1990年代之初以来,这一直未标准化,特别是因为脚本解释器的魔术数字机制已经在宇宙的AT&T端公开并exec…()System 5接口定义中得到了证明。:

解释器文件以以下形式的一行开头

#!路径名[arg]
其中pathname是解释器的路径,而arg是可选参数。当您exec使用解释器文件时,系统exec将使用指定的解释器。
exec系统V接口定义。第1卷,1991年。

不幸的是,今天的行为仍然和1980年代一样广泛地存在分歧,并且没有真正常见的行为可以标准化。某些Unices(例如著名的HP-UX和FreeBSD)不支持将脚本用作脚本的解释器。第一行是由空格分隔的一个,两个还是多个元素,在MacOS(以及2005之前的FreeBSD版本)之间以及其他之间有所不同。支持的最大路径长度会有所不同。 POSIX可移植文件名字符集之外的字符以及前导和尾随空格都非常棘手。第0,第1和第2参数的结果也是棘手的,整个系统之间存在很大差异。一些当前符合POSIX要求,但符合-Unix系统仍然不支持任何这种机制,并且强制将其转换为不再符合POSIX的机制。

进一步阅读


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.