Shebang中的多个论点


32

我想知道是否存在通过shebang行(#!)将多个选项传递给可执行文件的一般方法。

我使用NixOS,在我编写的任何脚本中,shebang的第一部分通常是/usr/bin/env。然后我遇到的问题是,系统将所有后续内容解释为单个文件或目录。

例如,假设我想编写一个bash在posix模式下执行的脚本。编写shebang的幼稚方式是:

#!/usr/bin/env bash --posix

但是尝试执行生成的脚本会产生以下错误:

/usr/bin/env: ‘bash --posix’: No such file or directory

我知道这篇文章,但我想知道是否有更通用,更清洁的解决方案。


编辑:我知道对于Guile脚本,有一种方法可以实现我想要的内容,在手册的4.3.4节中进行了介绍:

 #!/usr/bin/env sh
 exec guile -l fact -e '(@ (fac) main)' -s "$0" "$@"
 !#

这里的窍门是,第二行(以开头exec)被Guile解释器解释为代码,sh但在#!... !#块中但作为注释,因此被忽略。

不可能将这种方法推广到任何解释器吗?


第二次编辑:经过一番摸索之后,对于可以从中读取输入内容的解释器来说stdin,以下方法可行:

#!/usr/bin/env sh
sed '1,2d' "$0" | bash --verbose --posix /dev/stdin; exit;

但是,这可能不是最佳的,因为该sh过程一直持续到口译员完成工作为止。任何反馈或建议,将不胜感激。



Answers:


27

没有通用的解决方案,至少在不需要支持Linux的情况下没有,因为Linux内核将shebang行中第一个“单词”之后的所有内容都视为一个自变量

我不确定NixOS的约束是什么,但是通常我只是将您的shebang写成

#!/bin/bash --posix

或者,如果可能,在脚本中设置选项

set -o posix

另外,您可以通过适当的外壳程序调用来重新启动脚本:

#!/bin/sh -

if [ "$1" != "--really" ]; then exec bash --posix -- "$0" --really "$@"; fi

shift

# Processing continues

只要您找到目标语言忽略前几行(由Shell解释)的方法,这种方法就可以推广到其他语言。

从8.30版开始,GNU coreutils' env提供了一种解决方法,有关详细信息,请参见unode答案。(在Debian 10和更高版本,RHEL 8和更高版本,Ubuntu 19.04和更高版本等中可用)


18

尽管不是完全可移植,但是从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

很高兴知道这一点,以供将来参考。
John McGehee

该选项是从FreeBSD的复制env,其中-S在2005年见加入lists.gnu.org/r/coreutils/2018-04/msg00011.html
斯特凡Chazelas

在Fedora 29
Eric

@unode对showargs以下内容做了一些改进:pastebin.com/q9m6xr8Hpastebin.com/gS8AQ5WA(单线)
Eric

FYI:如的coreutils 8.31的,env包括它自己showargs-v选项#!/usr/bin/env -vS --option1 --option2 ...
chocolateboy

9

POSIX标准在描述时非常简洁#!

系统接口系列文档exec()基本原理部分

一些历史性实现处理Shell脚本的另一种方式是,将文件的前两个字节识别为字符串,#!并使用文件的第一行的其余部分作为要执行的命令解释器的名称。

在“ Shell简介”部分中

Shell从文件(请参阅参考资料sh),-c选项或POSIX.1-2008的System Interfaces卷中定义的system()popen()函数中读取其输入。如果Shell命令文件的第一行以字符开头#!,则结果不确定

基本上,这意味着任何实现(您使用的Unix)都可以根据需要随意进行shebang行的解析。

一些Unices,例如macOS(无法测试ATM),会将在shebang行上提供给解释器的参数拆分为单独的参数,而Linux和大多数其他Unices将这些参数作为单个选项提供给解释器。

因此,依靠shebang线能够接受多个论点是不明智的。

另请参阅Wikipedia上Shebang文章的“ 可移植性”部分


一个简单的解决方案(可以推广到任何实用程序或任何语言)是制作一个包装器脚本,该脚本使用适当的命令行参数执行真实的脚本:

#!/bin/sh
exec /bin/bash --posix /some/path/realscript "$@"

我不认为我会亲自尝试,使其重新执行本身的那种感觉有点脆弱。


7

shebang在execve(2)手册页中描述如下:

#! interpreter [optional-arg]

此语法接受两个空格:

  1. 解释程序路径之前有一个空格,但是此空格是可选的。
  2. 一个空格分隔解释程序路径及其可选参数。

请注意,在谈论可选参数时,我没有使用复数,上面的语法也没有使用[optional-arg ...],因为您最多只能提供一个参数

就外壳脚本而言,您可以set在脚本开头附近使用内置命令,该命令将允许设置解释器参数,提供与使用命令行参数相同的结果。

在您的情况下:

set -o posix

在Bash提示下,检查的输出help set以获取所有可用选项。


1
允许您有两个以上的空格,它们仅被视为可选参数的一部分。
斯蒂芬·基特

@StephenKitt:的确,这里的空格比实际的空格字符更多地被视为一个类别。我认为其他空白如制表符也应被广泛接受。
WhiteWinterWolf'17-10-22

3

在Linux上,shebang不太灵活;根据多个答案(Stephen Kitt的答案JörgW Mittag 的答案),在shebang行中没有指定的方法来传递多个参数。

我不确定它是否对任何人都有用,但是我写了一个简短的脚本来实现缺少的功能。参见https://gist.github.com/loxaxs/7cbe84aed1c38cf18f70d8427bed1efa

也可以编写嵌入式解决方法。在下面,我介绍了应用于相同测试脚本的四种与语言无关的变通方法,并且每一种都可以打印出结果。我想该脚本是可执行文件,位于中/tmp/shebang


在流程替换内部的bash heredoc中包装脚本

据我所知,这是最可靠的与语言无关的方式。它允许传递参数并保留标准输入。缺点是解释器不知道它读取的文件的(实际)位置。

#!/bin/bash
exec python3 -O <(cat << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv
try:
    print("input() 0 ::", input())
    print("input() 1 ::", input())
except EOFError:
    print("input() caused EOFError")
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")
EOWRAPPER
) "$@"

调用echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'打印:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /dev/fd/62
argv[1:]  :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: False
PYTHON_SCRIPT_END

请注意,进程替换会生成一个特殊文件。这可能不适合所有可执行文件。例如,#!/usr/bin/less抱怨:/dev/fd/63 is not a regular file (use -f to see it)

我不知道是否有可能在破折号中使用heredoc进行流程替换。


将脚本包装在一个简单的heredoc中

更简短,更简单,但是您将无法stdin从脚本中进行访问,并且它要求解释器能够从中读取和执行脚本stdin

#!/bin/sh
exec python3 - "$@" << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

try:
    print("input() 0 ::", input())
    print("input() 1 ::", input())
except EOFError:
    print("input() caused EOFError")
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")
EOWRAPPER

调用echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'打印:

PYTHON_SCRIPT_BEGINNING
input() caused EOFError
argv[0]   :: -
argv[1:]  :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: True
PYTHON_SCRIPT_END

使用awk system()调用但不带参数

正确传递执行文件的名称,但是脚本不会收到您提供的参数。请注意,awk是我所知道的唯一一种语言,默认情况下两者都安装在linux上,并且默认情况下从命令行读取其指令。

#!/usr/bin/gawk BEGIN {system("python3 -O " ARGV[1])}
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")

调用echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'打印:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /tmp/shebang
argv[1:]  :: []
__debug__ :: False
PYTHON_SCRIPT_END

system()如果您的参数不包含空格,请使用awk 4.1+ 调用

很好,但前提是您确定不会用包含空格的参数调用脚本。如您所见,除非对空格进行转义,否则包含空格的参数将被拆分。

#!/usr/bin/gawk @include "join"; BEGIN {system("python3 -O " join(ARGV, 1, ARGC, " "))}
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")

调用echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'打印:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /tmp/shebang
argv[1:]  :: ['arg1', 'arg2', 'contains', 'spaces', 'arg3 uses \\escapes\\']
__debug__ :: False
PYTHON_SCRIPT_END

对于4.1以下的awk版本,您将必须在for循环内使用字符串连接,请参见示例函数https://www.gnu.org/software/gawk/manual/html_node/Join-Function.html


1
引用此处的文件终止符以禁止$variable`command`替换:exec python3 -O <(cat <<'EOWRAPPER'
John McGehee

2

LD_LIBRARY_PATH#!(shebang)行上与python 一起使用的技巧除了外壳程序外不依赖其他任何东西,并且可以处理:

#!/bin/sh
'''' 2>/dev/null; exec /usr/bin/env LD_LIBRARY_PATH=. python -x "$0" "$@" #'''

__doc__ = 'A great module docstring'

如本页其他地方所述,某些shell sh可以在其标准输入中使用脚本。

我们提供的脚本会sh尝试执行''''被简化为''(空字符串)的命令,sh并且由于没有''命令,它当然无法执行,因此它通常line 2: command not found在标准错误描述符上输出,但是我们将此消息重定向2>/dev/null到最接近的黑洞,因为它会使用户感到混乱并使其混乱sh

然后,我们继续执行我们感兴趣的命令:exec在本例中/usr/bin/env python,它使用适当的参数替换了当前的shell程序:

  • "$0" 让python知道应该打开和解释哪个脚本,并进行设置 sys.argv[0]
  • "$@"将python设置sys.argv[1:]为在脚本命令行上传递的参数。

并且我们还要求env设置LD_LIBRARY_PATH环境变量,这是该技巧的唯一目的。

shell命令以注释开头,#以注释结尾,因此shell忽略尾随的三引号'''

sh然后被替换为python解释程序的新实例,该实例打开并读取作为第一个参数("$0")给出的python源脚本。

Python通过该-x参数打开文件并跳过源代码的第一行。注意:它也可以使用,-x因为对于Python来说shebang只是一个注释

然后,Python将第二行解释为当前模块文件的文档字符串,因此,如果您需要有效的模块文档字符串,只需__doc__像上面的示例一样在python程序中设置第一件事即可。



鉴于一个空字符串为…um…为空,您应该能够删除未找到猴子的命令:''''exec ...应该完成工作。注意exec之前没有空格,否则它将寻找空命令。你想空拼接到第一个参数,因此这样$0exec
Caleb

1

在寻找除脚本之外的可执行文件时,我发现了一个相当愚蠢的解决方法:

#!/usr/bin/awk BEGIN{system("bash --posix "ARGV[1])}
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.