在剧本的shebang行中,我怎么可能有不止一种可能性?


16

我处于一个有趣的情况,我有一个Python脚本,理论上可以由具有各种环境(和PATH)的各种用户以及各种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)不是一种选择。也不能选择依靠用户的PATH进行某种设置。

编辑:

版本的Python脚本内检查是不是因为我用它存在像Python 2.6的(并可以在2.5与所使用的“与”语句去上班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')

不是您真正想要的,因此发表评论。但是您可以import sys; sys.version_info()用来检查用户是否具有所需的python版本。
Bernhard

2
@Bernhard是的,这是真的,但是到那时再做任何事情都为时已晚。对于上面列出的第三种情况,我在上面直接运行脚本(即./script.py)将导致python2.4执行它,这将导致您的代码检测到它是错误的版本(并退出了,大概是这样)。但是有一个非常好的python2.5可以代替解释器!
user108471

2
使用包装器脚本找出是否存在合适的python,如果存在exec,请打印,否则输出错误。
凯文

1
因此,首先要在同一文件中。
凯文

9
@ user108471:您假设shebang行由bash处理。不是,它是系统调用(execve)。参数是字符串文字,没有glob,没有regexp。而已。即使第一个arg是“ / bin / bash”而第二个选项(“ -c ...”)也不会被shell解析。它们未经处理就交给了bash可执行文件,这就是为什么您会得到这些错误的原因。另外,shebang仅在开始时才起作用。因此,恐怕您不走运(缺少找到python解释器并向其提供HERE文档的脚本,听起来像是一团糟)。
goldilocks

Answers:


13

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

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

#!/usr/bin/env python

要么

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

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

通常是一个不错的选择

#!/usr/bin/env python

它会在整个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

2
我很高兴自己想做的事与被认为是好的做法背道而驰。您发布的内容完全是合理的,但与此同时,这也不是我要的。我不希望用户在我关心的所有情况下都完全有可能检测到适当版本的Python时,不必将我的脚本明确指向适当版本的Python。
user108471

1
请查看我的更新,以了解为什么我不能“仅检查它是否在Python 2.4中运行并打印一些信息并退出”。
user108471

你是对的。我只是在SO上发现了这个问题,现在我可以看到,如果您只想拥有一个文件,则没有其他选择...
pbm

9

根据一些评论中的一些想法,我设法拼凑出了一个看起来确实有效的丑陋的骇客工具。该脚本变成了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版本将脚本的其余部分解释为here文档。

关于一些恶作剧的解释:

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

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

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


也许毕竟还不那么讨厌;)+1
goldilocks 2013年

这样做的缺点是,它会使大多数编辑器的语法颜色混乱
Lie Ryan

@LieRyan这取决于。我的脚本使用.py文件扩展名,大多数文本编辑器在选择用于着色的语法时会首选此扩展名。假设,如果我不使用.py扩展名重命名,则可以使用modeline来提示正确的语法(至少对于Vim用户而言),例如:# ft=python
user108471 2013年

7

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

如果你想要把一切都放在一个文件,你可以经常使它成为一个通晓多种语言,与一开始#!/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字符串,没有任何作用。对于外壳程序,第二行是''':'在除去引号之后是no-op命令:。)


第二种解决方案很好,因为它不需要# EOF像在此答案中最后添加。您的方法基本上与此处概述的方法相同。
sschuberth 2013年

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")

为我的python致歉


执行后不会继续运行吗?所以普通的东西会被执行两次?
Janus Troelsen

1
execv用新加载的程序映像替换当前正在执行的程序
Matt

看起来这是一个很棒的解决方案,比我想出的解决方案感觉不那么丑陋。我必须尝试一下,看看它是否可以达到这个目的。
user108471

这个建议几乎可以满足我的需求。唯一的缺陷是我最初没有提到的东西:为了支持Python 2.5,我使用from __future__ import with_statement,这必须是Python脚本中的第一件事。我不认为您在启动新解释器时碰巧知道执行该操作的方法吗?
user108471

你确定它需要是非常第一件事?还是在尝试使用with像常规导入之类的s 之前?if mypy == 25: from __future__ import with_statement在“常规内容”之前是否需要额外的 工作?如果您不支持2.4,则可能不需要if。
马特

0

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

#!/my/python/search/script

这个脚本只是做(在搜索之后):

"$python_path" "$1"

我不确定内核是否会接受此脚本间接访问,但是我检查了它是否可以工作。

编辑1

最后,要使这种令人尴尬的感知力成为一个好建议:

可以将两个脚本合并到一个文件中。您只需在bash脚本中将python脚本编写为here文档(如果您更改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

这或多或少是在解决方案的最后一段中已经说明的解决方案!
伯恩哈德

聪明,但是它要求将这个魔术脚本安装在每个系统的某个位置。
user108471

@Bernhard Oooops,被抓住了。以后我会读到最后。作为补偿,我将其改进为一个文件解决方案。
Hauke Laging

@ user108471魔术脚本可以包含以下内容:$(ls /usr/bin/python?.? | tail -n1 )但是我没有成功在shebang中巧妙地使用它。
Bernhard

@Bernhard您想在shebang行中进行搜索吗?IIRC内核不关心在shebang行中引用。否则(如果与此同时发生了变化)可以执行类似## / bin / bash -c do_search_here_without_whitespace ...; exec $ python“ $ 1”的操作,但是如果没有空格怎么办呢?
Hauke Laging
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.