我想知道是否有一种通用方法可以通过 shebang 行 ( #!
) 将多个选项传递给可执行文件。
我使用 NixOS,我编写的任何脚本中 shebang 的第一部分通常是/usr/bin/env
.我遇到的问题是,后面的所有内容都被系统解释为单个文件或目录。
例如,假设我想编写一个bash
在 posix 模式下执行的脚本。编写 shebang 的简单方式是:
#!/usr/bin/env bash --posix
但尝试执行生成的脚本会产生以下错误:
/usr/bin/env: ‘bash --posix’: No such file or directory
我知道这个帖子,但我想知道是否有更通用和更干净的解决方案。
编辑: 我知道对于诡计脚本,有一种方法可以实现我想要的,记录在第 4.3.4 节手册的:
#!/usr/bin/env sh
exec guile -l fact -e '(@ (fac) main)' -s "$0" "$@"
!#
这里的技巧是,第二行(以 开头exec
)被 but 解释为代码sh
,位于#!
...!#
块中,作为注释,因此被 Guile 解释器忽略。
难道不能将此方法推广到任何解释器吗?
第二次编辑:经过一番尝试后,对于可以从 读取输入的解释器来说stdin
,以下方法似乎可行:
#!/usr/bin/env sh
sed '1,2d' "$0" | bash --verbose --posix /dev/stdin; exit;
不过,这可能不是最佳的,因为该sh
过程会一直持续到解释器完成其工作为止。任何反馈或建议将不胜感激。
答案1
虽然不完全可移植,但从 coreutils 8.30 开始根据其文档您将能够使用:
#!/usr/bin/env -S command arg1 arg2 ...
所以给出:
$ cat test.sh
#!/usr/bin/env -S showargs here 'is another' long arg -e "this and that " too
你会得到:
% ./test.sh
$0 is '/usr/local/bin/showargs'
$1 is 'here'
$2 is 'is another'
$3 is 'long'
$4 is 'arg'
$5 is '-e'
$6 is 'this and that '
$7 is 'too'
$8 is './test.sh'
如果你好奇的showargs
是:
#!/usr/bin/env sh
echo "\$0 is '$0'"
i=1
for arg in "$@"; do
echo "\$$i is '$arg'"
i=$((i+1))
done
答案2
没有通用的解决方案,至少如果你需要支持Linux则没有,因为Linux 内核将 shebang 行中第一个“单词”之后的所有内容视为单个参数。
我不确定 NixOS 的限制是什么,但通常我会把你的 shebang 写为
#!/bin/bash --posix
或者,在可能的情况下,在脚本中设置选项:
set -o posix
或者,您可以使用适当的 shell 调用让脚本自行重新启动:
#!/bin/sh -
if [ "$1" != "--really" ]; then exec bash --posix -- "$0" --really "$@"; fi
shift
# Processing continues
这种方法可以推广到其他语言,只要您找到一种方法让目标语言忽略前几行(由 shell 解释)。
GNU coreutils
'env
自版本 8.30 起提供了解决方法,请参阅乌节点的回答了解详情。 (这在 Debian 10 及更高版本、RHEL 8 及更高版本、Ubuntu 19.04 及更高版本等中可用)
答案3
POSIX 标准的描述非常简洁#!
:
一些历史实现处理 shell 脚本的另一种方法是将文件的前两个字节识别为字符串
#!
,并使用文件第一行的其余部分作为要执行的命令解释器的名称。
来自外壳介绍部分:
shell 从文件(请参阅 参考资料
sh
)、-c
选项或POSIX.1-2008 系统接口卷中定义的system()
和函数读取其输入。popen()
如果 shell 命令文件的第一行以字符开头#!
,则结果未指定。
这基本上意味着任何实现(您正在使用的 Unix)都可以根据需要自由地执行 shebang 行的解析细节。
一些 Unices,如 macOS(无法测试 ATM),会将 shebang 行上提供给解释器的参数拆分为单独的参数,而 Linux 和大多数其他 Unices 会将参数作为单个选项提供给解释器。
因此,依赖 shebang 线能够接受多个参数是不明智的。
一种简单的解决方案可以推广到任何实用程序或语言,即制作一个包装脚本,使用适当的命令行参数执行实际脚本:
#!/bin/sh
exec /bin/bash --posix /some/path/realscript "$@"
我不认为我个人会尝试让它重新执行本身因为这感觉有点脆弱。
答案4
shebang 描述于execve
(2)手册页如下:
#! interpreter [optional-arg]
此语法接受两个空格:
- 前面一个空格口译员路径,但这个空格是可选的。
- 一个空格分隔口译员路径及其可选参数。
请注意,在谈论可选参数时我没有使用复数,上面的语法也没有使用[optional-arg ...]
,如您最多可以提供一个参数。
就 shell 脚本而言,您可以set
在脚本开头附近使用内置命令,这将允许设置解释器参数,提供与使用命令行参数相同的结果。
在你的情况下:
set -o posix
从 Bash 提示符中,检查 的输出help set
以获取所有可用选项。