我怎样才能在脚本的 shebang 行中拥有不止一种可能性?

我怎样才能在脚本的 shebang 行中拥有不止一种可能性?

我的情况有点有趣,我有一个 Python 脚本,理论上可以由各种用户在各种环境(和路径)和各种 Linux 系统上运行。我希望这个脚本可以在尽可能多的脚本上执行,而无需人为限制。以下是一些已知的设置:

  • Python 2.6是系统Python版本,因此python、python2和python2.6都存在于/usr/bin中(并且是等效的)。
  • 如上所述,Python 2.6 是系统 Python 版本,但 Python 2.7 与它一起安装为 python2.7。
  • Python 2.4是系统Python版本,我的脚本不支持。在 /usr/bin 中,我们有等效的 python、python2 和 python2.4,以及脚本支持的 python2.5。

我想在这三个上运行相同的可执行 python 脚本。如果它首先尝试使用 /usr/bin/python2.7(如果存在),那么会很好,然后回退到 /usr/bin/python2.6,然后回退到 /usr/bin/python2.5,然后如果这些都不存在,则简单地出错。不过,我并不太热衷于使用最新的 2.x,只要它能够找到正确的解释器之一(如果存在)。

我的第一个想法是将 shebang 行从:

#!/usr/bin/python

#!/usr/bin/python2.[5-7]

因为这在 bash 中运行良好。但运行脚本时出现以下错误:

/usr/bin/python2.[5-7]: bad interpreter: No such file or directory

好的,所以我尝试以下方法,这在 bash 中也有效:

#!/bin/bash -c /usr/bin/python2.[5-7]

但同样,这失败了:

/bin/bash: - : invalid option

好吧,显然我可以编写一个单独的 shell 脚本来找到正确的解释器并使用它找到的任何解释器运行 python 脚本。我只是发现分发两个文件很麻烦,只要它运行在安装了最新的 python 2 解释器的情况下,一个文件就足够了。要求人们明确地调用解释器(例如,$ python2.5 script.py)不是一个选择。依赖以某种方式设置的用户路径也是不可行的。

编辑:

Python 脚本中的版本检查是不是因为我使用的是 Python 2.6 中存在的“with”语句(并且可以在 2.5 中使用 with from __future__ import with_statement),所以它可以工作。这会导致脚本立即失败,并出现用户不友好的 SyntaxError,并阻止我有机会首先检查版本并发出适当的错误。

例子:(使用低于 2.6 的 Python 解释器尝试此操作)

#!/usr/bin/env python

import sys

print "You'll never see this!"
sys.exit()

with open('/dev/null', 'w') as out:
    out.write('something')

答案1

我不是专家,但我相信您不应该指定要使用的确切 python 版本并将该选择留给系统/用户。

另外,您应该使用它而不是在脚本中硬编码 python 路径:

#!/usr/bin/env python

或者

#!/usr/bin/env python3 (or python2)

这是受到推崇的 经过 Python 文档在所有版本中:

一个好的选择通常是

#!/usr/bin/env python

它在整个路径中搜索Python解释器。但是,某些 Unices 可能没有 env 命令,因此您可能需要将 /usr/bin/python 硬编码为解释器路径。

在不同的发行版中,Python 可能安装在不同的地方,因此env将在 中搜索它PATH。它应该在所有主要的 Linux 发行版中可用,并且据我所知在 FreeBSD 中也可用。

脚本应使用您的 PATH 中并由您的发行版选择的 Python 版本执行*。

如果您的脚本与除 2.4 之外的所有版本的 Python 兼容,您应该检查它的内部是否在 Python 2.4 中运行并打印一些信息并退出。

更多内容请阅读

  • 这里您可以找到有关 Python 可能安装在不同系统中的位置的示例。
  • 这里您可以发现使用的一些优点和缺点env
  • 这里您可以找到 PATH 操作和不同结果的示例。

脚注

*在 Gentoo 中有一个名为 的工具eselect。使用它你可以将不同应用程序(包括Python)的默认版本设置为默认:

$ eselect python list
Available Python interpreters:
  [1]   python2.6
  [2]   python2.7 *
  [3]   python3.2
$ sudo eselect python set 1
$ eselect python list
Available Python interpreters:
  [1]   python2.6 *
  [2]   python2.7
  [3]   python3.2

答案2

根据一些评论中的一些想法,我设法拼凑出一个真正丑陋的黑客,但似乎有效。该脚本成为 bash 脚本,包装了 Python 脚本,并通过“此处文档”将其传递给 Python 解释器。

一开始:

#!/bin/bash

''':'
vers=( /usr/bin/python2.[5-7] )
latest="${vers[$((${#vers[@]} - 1))]}"
if !(ls $latest &>/dev/null); then
    echo "ERROR: Python versions < 2.5 not supported"
    exit 1
fi
cat <<'# EOF' | exec $latest - "$@"
''' #'''

Python 代码位于此处。然后在最后:

# EOF

当用户运行脚本时,将使用 2.5 和 2.7 之间的最新 Python 版本将脚本的其余部分解释为此处文档。

对一些恶作剧的解释:

我添加的三引号内容还允许将相同的脚本作为 Python 模块导入(我将其用于测试目的)。当由 Python 导入时,第一个和第二个三重单引号之间的所有内容都被解释为模块级字符串,第三个三重单引号被注释掉。剩下的就是普通的Python了。

当直接运行时(现在作为 bash 脚本),前两个单引号变成空字符串,第三个单引号与第四个单引号形成另一个字符串,仅包含一个冒号。 Bash 将此字符串解释为无操作。其他一切都是 Bash 语法,用于在 /usr/bin 中匹配 Python 二进制文件,选择最后一个,然后运行 ​​exec,将文件的其余部分作为此处文档传递。这里的文档以 Python 三单引号开头,仅包含哈希/磅/八索普符号。然后,脚本的其余部分将被正常解释,直到读取“# EOF”的行终止此处文档。

我觉得这很不正常,所以我希望有人有更好的解决方案。

答案3

shebang 行只能指定解释器的固定路径。有一个#!/usr/bin/env技巧可以在 中查找解释器,PATH但仅此而已。如果您想要更复杂,则需要编写一些包装器 shell 代码。

最明显的解决方案是编写一个包装脚本。调用 python 脚本foo.real并制作包装脚本foo

#!/bin/sh
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0.real" "$@"
else
  exec python "$0.real" "$@"
fi

如果您想将所有内容放入一个文件中,通常可以将其设为一个多语言以一行开头#!/bin/sh(因此将由 shell 执行),但也是另一种语言的有效脚本。根据语言的不同,多语言可能是不可能的(#!例如,如果导致语法错误)。在Python 中,这并不是很困难。

#!/bin/sh
''':'
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0.real" "$@"
else
  exec python "$0.real" "$@"
fi
'''
# real Python script starts here
def …

'''(和之间的整个文本'''是顶层的 Python 字符串,没有任何作用。对于 shell,第二行是''':'去掉引号后的 which 是 no-op 命令:。)

答案4

您可以编写一个小的 bash 脚本来检查可用的 phython 可执行文件并使用该脚本作为参数来调用它。然后,您可以将此脚本设为 shebang 行目标:

#!/my/python/search/script

这个脚本只是执行以下操作(搜索后):

"$python_path" "$1"

我不确定内核是否会接受这个脚本间接,但我检查了一下,它有效。

编辑1

为了使这种令人尴尬的无知最终成为一个好的建议:

可以将两个脚本合并到一个文件中。您只需将 python 脚本编写为 bash 脚本中的此处文档(如果更改 python 脚本,则只需再次将脚本复制在一起)。您可以在例如 /tmp 中创建一个临时文件,或者(如果 python 支持,我不知道)您提供脚本作为解释器的输入:

# do the search here and then
# either
cat >"tmpfile" <<"EOF" # quoting EOF is important so that bash leaves the python code alone
# here is the python script
EOF
"$python_path" "tmpfile"
# or
"$python_path" <<"EOF"
# here is the python script
EOF

相关内容