在脚本启动后选择解释器,例如在 hashbang 中的 if/else

dkv*_*dkv 17 shell shell-script exec shebang interpreter

有没有办法动态选择正在执行脚本的解释器?我有一个在两个不同系统上运行的脚本,我想使用的解释器位于两个系统上的不同位置。我最终不得不在每次切换时更改 hashbang 行。我想做一些与此逻辑等效的事情(我意识到这种确切的构造是不可能的):

if running on system A:
    #!/path/to/python/on/systemA
elif running on system B:
    #!/path/on/systemB

#Rest of script goes here
Run Code Online (Sandbox Code Playgroud)

或者更好的是这样,以便它尝试使用第一个解释器,如果没有找到,则使用第二个解释器:

try:
    #!/path/to/python/on/systemA
except: 
    #!path/on/systemB

#Rest of script goes here
Run Code Online (Sandbox Code Playgroud)

显然,我可以 根据我所在的位置/path/to/python/on/systemA myscript.py/path/on/systemB myscript.py根据我所在的位置来执行它 ,但我实际上有一个启动的包装脚本myscript.py,所以我想以编程方式而不是手动指定 python 解释器的路径。

ilk*_*chu 28

您始终可以制作一个包装脚本来为实际程序找到正确的解释器:

#!/bin/bash
if something ; then
    interpreter=this
    script=/some/path/to/program.real
    flags=()
else
    interpreter=that
    script=/other/path/to/program.real
    flags=(-x -y)
fi
exec "$interpreter" "${flags[@]}" "$script" "$@"
Run Code Online (Sandbox Code Playgroud)

将包装器保存在用户的PATHas 中program,并将实际程序放在一边或使用其他名称。

#!/bin/bash由于flags数组,我在 hashbang 中使用。如果您不需要存储可变数量的标志等并且可以不用它,则该脚本应该可移植地使用#!/bin/sh.

  • 我见过 `exec "$interpreter" "${flags[@]}" "$script" "$@"` 也用来保持进程树更干净。它还传播退出代码。 (2认同)
  • 用 `#!/bin/sh` 代替 `#!/bin/bash` 不是更好吗?即使“/bin/sh”是到不同 shell 的符号链接,它也应该存在于大多数(如果不是全部)*nix 系统上,而且它会迫使脚本作者制作可移植的脚本而不是陷入 bashisms。 (2认同)

Kus*_*nda 26

不,那行不通。这两个字符#!绝对需要是文件中的前两个字符(无论如何,您将如何指定解释 if 语句的内容?)。这构成了exec()函数族在确定它们将要执行的文件是脚本(需要解释器)还是二进制文件(不需要解释器)时检测到的“幻数” 。

shebang行的格式相当严格。它需要有一个到解释器的绝对路径和最多一个参数。

可以做的是使用env

#!/usr/bin/env interpreter
Run Code Online (Sandbox Code Playgroud)

现在,路径env一般 /usr/bin/env,但在技术上这是没有保证。

这允许您调整PATH每个系统上的环境变量,以便找到interpreter(无论是它bashpython还是perl您拥有的任何东西)。

这种方法的缺点是不可能将参数可移植地传递给解释器。

这意味着

#!/usr/bin/env awk -f
Run Code Online (Sandbox Code Playgroud)

#!/usr/bin/env sed -f
Run Code Online (Sandbox Code Playgroud)

不太可能在某些系统上工作。

另一个明显的方法是使用 GNU 自动工具(或一些更简单的模板系统)来查找解释器并将正确的路径放入文件中./configure,这将在每个系统上安装脚本时运行。

也可以使用显式解释器运行脚本,但这显然是您要避免的:

$ sed -f script.sed
Run Code Online (Sandbox Code Playgroud)

  • @dkv 在 Linux 上运行 `/usr/bin/env`,带有单个参数 `awk -f`。 (3认同)
  • @dkv 不是。它使用带有两个参数的解释器,它可能适用于某些系统,但绝对不适用于所有系统。 (2认同)

Osk*_*kog 12

您还可以编写多语言(组合两种语言)。/bin/sh 保证存在。

这有丑陋代码的缺点,也许某些/bin/shs 可能会混淆。但是它可以在env不存在或存在于 /usr/bin/env 之外的其他地方时使用。如果您想做一些漂亮的选择,也可以使用它。

脚本的第一部分确定在 /bin/sh 作为解释器运行时使用哪个解释器,但在由正确的解释器运行时被忽略。使用exec以防止shell比第一部分运转更。

蟒蛇示例:

#!/bin/sh
'''
' 2>/dev/null
# Python thinks this is a string, docstring unfortunately.
# The shell has just tried running the <newline> program.
find_best_python ()
{
    for candidate in pypy3 pypy python3 python; do
        if [ -n "$(which $candidate)" ]; then
            echo $candidate
            return
        fi
    done
    echo "Can't find any Python" >/dev/stderr
    exit 1
}
interpreter="$(find_best_python)"   # Replace with something fancier.
# Run the rest of the script
exec "$interpreter" "$0" "$@"
'''
Run Code Online (Sandbox Code Playgroud)

  • Scala 实际上在其语法中支持多语言脚本:如果 Scala 脚本以 `#!` 开头,Scala 会忽略匹配的 `!#` 之前的所有内容;这允许您将任意语言的任意复杂脚本代码放入其中,然后使用脚本`exec` Scala 执行引擎。 (6认同)
  • 我想我以前看过其中之一,但这个想法仍然同样糟糕......实际翻译也是如此。(然后希望没有人在设置 `$0` 时撒谎。) (3认同)