如何在脚本的shebang行中拥有不止一种可能性?

use*_*471 19 scripting bash python

我遇到了一个有趣的情况,我有一个 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
Run Code Online (Sandbox Code Playgroud)

#!/usr/bin/python2.[5-7]
Run Code Online (Sandbox Code Playgroud)

因为这在 bash 中工作正常。但是运行脚本会给出:

/usr/bin/python2.[5-7]: bad interpreter: No such file or directory
Run Code Online (Sandbox Code Playgroud)

好的,所以我尝试以下操作,这也适用于 bash:

#!/bin/bash -c /usr/bin/python2.[5-7]
Run Code Online (Sandbox Code Playgroud)

但同样,这失败了:

/bin/bash: - : invalid option
Run Code Online (Sandbox Code Playgroud)

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

编辑:

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

示例:(用小于 2.6 的 Python 解释器试试这个)

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

pbm*_*pbm 13

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

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

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

或者

#!/usr/bin/env python3 (or python2)
Run Code Online (Sandbox Code Playgroud)

这是建议 Python的 文档中的所有版本:

一个好的选择通常是

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

它在整个 PATH 中搜索 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
Run Code Online (Sandbox Code Playgroud)

  • 我很欣赏我想做的事情与被认为是好的做法背道而驰。您发布的内容完全合理,但同时也不是我所要求的。我不希望我的用户必须明确地将我的脚本指向合适的 Python 版本,因为在我关心的所有情况下完全有可能检测到合适的 Python 版本。 (2认同)

use*_*471 11

根据一些评论中的一些想法,我设法拼凑出一个看似有效的真正丑陋的 hack。该脚本变成了一个 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 - "$@"
''' #'''
Run Code Online (Sandbox Code Playgroud)

Python 代码放在这里。然后在最后:

# EOF
Run Code Online (Sandbox Code Playgroud)

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

关于一些恶作剧的解释:

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

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

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


Gil*_*il' 8

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

最明显的解决方案是编写一个包装脚本。调用 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
Run Code Online (Sandbox Code Playgroud)

如果您想将所有内容都放在一个文件中,您通常可以将其设为以行开头的多语言#!/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 …
Run Code Online (Sandbox Code Playgroud)

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


Mat*_*att 6

由于您的需求陈述了已知的二进制文件列表,您可以使用以下命令在 Python 中完成。它不会超过一个位数的次要/主要版本的 Python,但我认为这不会很快发生。

如果二进制文件上标记的版本高于当前执行的 python 版本,则从有序的、递增的版本化 python 列表中运行位于磁盘上的最高版本。“有序递增版本列表”是此代码的重要部分。

#!/usr/bin/env python
import os, sys

pythons = [ '/usr/bin/python2.3','/usr/bin/python2.4', '/usr/bin/python2.5', '/usr/bin/python2.6', '/usr/bin/python2.7' ]
py = list(filter( os.path.isfile, pythons ))
if py:
  py = py.pop()
  thepy = int( py[-3:-2] + py[-1:] )
  mypy  = int( ''.join( map(str, sys.version_info[0:2]) ) )
  if thepy > mypy:
    print("moving versions to "+py)
    args = sys.argv
    args.insert( 0, sys.argv[0] )
    os.execv( py, args )

print("do normal stuff")
Run Code Online (Sandbox Code Playgroud)

为我沙哑的蟒蛇道歉