我想将 shebang 放入#!/bin/sh -eufo pipefail
我的脚本中。但有几个地方很奇怪:
- 该脚本在 FreeBSD 中会因该 shebang 而失败,但在 MacOS 上运行时不会失败
- 在 FreeBSd 上,当直接从命令行执行时,相同的 shebang 可以工作(也
/bin/sh
)。
>>> sh -eufo pipefail -c 'echo hi' # this works
hi
>>> cat <<EOF > script
#!/bin/sh -eufo pipefail
echo hi
EOF
>>> chmod +x ./script
>>> ./script # this doesn't work on FreeBSD but works on MacOS
Illegal option -o ./script
>>> cat ./script
#!/bin/sh -eufo pipefail
echo hi
>>> uname -a
FreeBS 11.3-RELEASE-p7
答案1
#!
MacOS 仍然保留了 2005 年之前旧的 FreeBSD 行为。2005 年,FreeBSD 内核在传递给 的可执行文件的开头处理的方式发生了重大变化execve()
,以使其与其他一些操作系统内核更加一致,包括 Linux 和 NetBSD 内核。
NetBSD 内核源代码中的注释试图将此描绘为通用的:
* 收集 shell 参数。 shell 名称后面的所有内容 * 作为一个参数传递;这是正确的(历史) * 行为。——kern/exec-script.c
。网络BSD。第 189 行及以下。
事实上并非如此。 Sven Mascheck 大约十年前做了一些测试,有四就基本行为而言,AT&T Unix System 5 与 4.2BSD 一样具有“正确的历史”行为:
- 忽略字符(4.2BSD 和 AT&T Unix System 5 之前)。
- 在单个参数中传递整个字符串(从 2005 年起,4.2BSD、NetBSD、Linux 和 FreeBSD)。
- 用空格分割字符串并将其作为多个参数传递(2005 年之前的 FreeBSD 和 MacOS)。
- 用空格分割字符串并仅传递第一个参数(AT&T Unix System 5 和 Solaris)
我仅在括号中包含与此答案相关的操作系统。 M. Mascheck 检查了更多内容,就像 Ahmon Dancy 在 FreeBSD 问题报告 16393 的讨论中所做的那样。有关完整列表,请参阅进一步阅读。
具有讽刺意味的是,2005 年 FreeBSD 的关键在于,FreeBSD 并没有相当就如此容易。它引入了一项更改,旨在使有关 Perl 的流行书籍中所写的内容真正起作用:在注释字符后跳过参数。这些书推荐了以下内容:
#!/bin/sh -- # -*- perl -*-— 拉里·沃尔、汤姆·克里斯蒂安森、乔恩·奥尔旺特 (2000)。Perl 编程:第三版。奥莱利媒体。 ISBN 9780596000271。p。 488.
2000 年的 PR 16393 是一种让内核处理可执行 Perl 脚本的方法,其编写方式正是 Larry Wall 所说的可行的。然而,它破坏了其他东西并且没有完全起作用。
在这件事上有一些来来回回。最后,在 2005 年,使 Larry Wall 等人的想法发挥作用的机制被移出内核,该机制被设计为与 Linux、NetBSD 和 4.2BSD(而不是 Solaris 和 AT&T Unix System 5)兼容,并使得的责任sh
。
因此,自 2005 年以来的行为是 shell 获取三个参数,第二个参数是该#!
行的整个尾部,直接使用调用脚本execve()
实际上与调用相同:
sh '-eufo pipelinefail' ./script
很明显为什么 Almquist shell(sh
FreeBSD 上的)认为这./script
是该选项的选项参数-o
,并且它将该pipefail
部分视为后面收集的进一步的单字母选项-
(它没有得到这个选项)。尚未处理)。
另一个明显的替代方案是set -o pipefail
作为脚本中的第一个命令,如所指出的https://unix.stackexchange.com/a/533418/5132为了谍影重重壳。这仅被添加到 FreeBSD 中阿尔姆奎斯特然而,shell 是在 2019 年推出的,因此仅在最新版本的 FreeBSD 中可用。 (这德班截至 2020 年,Almquist shell 尚未添加它。)
进一步阅读
- 一些旧历史的指针:https://unix.stackexchange.com/a/489688/5132
- 斯文·马斯切克。 ”各种系统的测试结果”。
#!
关于各种 Unix 风格上的 shebang/hash-bang 机制的神奇细节。www.in-ulm.de/~mascheck。 - 加兰斯·A·德罗西恩 (Garance A Drosihn) (2005-02-23)。# 中的错误!处理——再来一次。 freebsd-arch 邮件列表。
- 莱安德(2000-01-27)。
/bin/sh
不删除 shebang 线上的评论。 FreeBSD 问题报告 16393。 - 加兰斯·A·德罗西恩 (2005)。更新 2005 年 5 月 28 日的说明:shell 脚本选项处理的更改。 people.freebsd.org/~gad。
sh
。 BSD通用命令手册。 2019年2月24日。 freebsd.org。- 沃尔夫拉姆·施奈德 (2017-12-12)。获取通过管道传输到另一个进程的退出状态:
set -o pipefail
缺少/bin/sh
。 FreeBSD 问题报告 224270。 - 易卜拉欣·加扎勒 (2020-06-30)。状态
set -o pipefail
。 Debian Almquist shell 邮件列表。
答案2
即使您的#!
用法是不可移植的,因为它使用的不仅仅是简单的:
#!/bin/sh
或普遍支持的单一参数,如下所示:
#!/bin/sh -oneflag
真正的问题是您使用的是不可移植的选项-o pipefail
。
ksh93
这是其他 shell不bash
支持的选项。
背景:
在 MacOS 上,
/bin/sh
它bash
已以特定方式编译(例如,echo
默认情况下使转义序列起作用)以使其符合 POSIX 标准。由于 bash 支持pipefail
(见上文),因此它可以在 MacOS 上运行。在 FreeBSD 上,
/bin/sh
不ash
支持pipefail
.
你有两种可能的方法:
不使用
set -o pipefail
等两年再试试。这很可能会起作用,因为我们决定在 10 个月前将此选项添加到下一个 POSIX 标准(第 8 期)中,请参阅https://www.austingroupbugs.net/view.php?id=789由于下一个 POSIX 标准将在大约 2019 年发布。一年后,FreeBSD 很有可能很快就会添加
set -o pipefail
支持ash
。