使用“#!/usr/bin/env python”无法向Python传递参数。

94

我需要一个可直接执行的Python脚本,因此我在文件开头加入了#!/usr/bin/env python。但是,我还需要无缓冲输出,所以我尝试了#!/usr/bin/env python -u,但会出现python -u: no such file or directory的错误。

我发现#/usr/bin/python -u可以工作,但我需要让pythonPATH中支持虚拟env环境。

我的选择有哪些?


2
你可以看一下这个SO问题,了解如何进行无缓冲输出的相关信息。 - Mattias Nilsson
9个回答

45

在一些环境中,env不会分割参数。 因此你的env正在路径中查找python -u。 我们可以使用sh来解决这个问题。 将你的shebang替换为以下代码行,一切都会正常。

#!/bin/sh
''''exec python -u -- "$0" ${1+"$@"} # '''
# vi: syntax=python

顺便说一下,我们不需要担心sh的路径,对吧?


5
想知道这是如何工作的人可以参考:为什么这段代码片段能够正常运行? - Martijn Pieters
16
${1+"$@"} hack」可能已经20年没必要使用了 :) 该技巧的具体内容可参考链接:http://www.perl.com/doc/FMTEYEWTK/sh_dollar_at - user4815162342
3
黑客技巧也许是不必要的,但它没有害处,对吧?知道这些很有趣 :-) 我今天刚学会。无论如何,我认为 "exec" "python" "-u" "--" "$0" "$@" 可能更容易理解 - 它有什么缺陷吗?(我认为它与 1+ hack 不兼容?) - Aaron McDaid
2
我的方法有一个缺点。如果你想传递一些复杂的东西给bash,比如嵌套的'"字符串,那么你的方法更可靠。这是一个有趣的问题!也许我的方法更容易理解,但你的方法更健壮。也许你的答案应该澄清必须以''''exec开头,并且字符串必须以# '''结尾(在#之前有一个空格)。只要我们遵循这些规则,而且没有任何额外的三重引号''',你的方法就是完美和灵活的。 - Aaron McDaid
1
@user4815162342 这里提供了${1+"$@"}的更多上下文信息。因此,在大多数情况下,"$@"本身应该可以正常工作。 - akhan
显示剩余3条评论

36

这可能有点过时,但 env(1) 手册告诉我们可以在那种情况下使用“-S”

#!/usr/bin/env -S python -u

它似乎在FreeBSD上表现得相当不错。


哇,如果这能被广泛使用就太好了,但它也不支持Cygwin :( - philwalk
7
似乎-S选项是针对BSD变种的env(1),但了解这一点很好。 - nodakai
7
现在Linux也有env -S了,自从coreutils 8.30以后就有了[1](可能需要一些时间才能在您的发行版上出现)。与FreeBSD的env(1)具有相同的语义-值得称赞的好功能的可移植性。 - Juan
在Solaris(11.3)上也没有成功... :( - Timmah
#!/usr/bin/env -S node --inspect - 可以工作! - Dariusz Filipiak

31

2
你需要在每次重启时都这样做吗? - dan
1
@dan:在同一个 shell 中不会改变。在使用 export 导出变量后,它将保持设置,直到您覆盖或使用 unset 取消设置。 - artu-hnrq

16

当你在Linux上使用shebang时,解释器名称后的整个剩余行被解释为单个参数。 python -u 就像你打了 /usr/bin/env 'python -u' 一样传递给了 env/usr/bin/env 查找名为 python -u 的二进制文件,但实际上不存在这样一个文件。


12

在Linux中,将参数传递给shebang行并不是标准的做法,而且在你尝试使用env时无法正常工作。对于bash,解决方法是使用内置命令“set”来设置所需的选项。我认为你可以使用Python命令来设置stdin的非缓冲输出,方法类似。

以上为个人意见。


8

这里提供了一个替代 /usr/bin/env 的脚本,可以在 hash-bang 行上传递参数,基于 /bin/bash 并且限制执行路径中不允许有空格。它被称作 "envns" (env No Spaces)。

#!/bin/bash

ARGS=( $1 )  # separate $1 into multiple space-delimited arguments.
shift # consume $1

PROG=`which ${ARGS[0]}`
unset ARGS[0] # discard executable name

ARGS+=( "$@" ) # remainder of arguments preserved "as-is".
exec $PROG "${ARGS[@]}"

假设此脚本位于 /usr/local/bin/envns,请使用以下 shebang 行:
```#!/usr/local/bin/envns```
#!/usr/local/bin/envns python -u

测试过在Ubuntu 13.10和cygwin x64上运行。


2
这应该被捆绑 :) - wieczorek1990
注意:大多数类Unix的#!实现不允许使用脚本出于安全原因。我很惊讶这在Ubuntu 13.10上能够工作。 - Juan
截至2019年1月20日,此程序在Ubuntu 16.04上也可以运行,其他版本尚不确定。 - philwalk

4
这是一个不太规范的方法,需要使用bash才能实现,但它可以运行:
#!/bin/bash

python -u <(cat <<"EOF"
# Your script here
print "Hello world"
EOF
)

1
谢谢,这对我解决另一个问题非常有帮助(使用带参数的Mono C# Shell)。 - IanNorton

3

延续Larry Cai的回答,env允许您直接在命令行中设置变量。这意味着可以在执行python命令之前使用等效的PYTHONUNBUFFERED设置来替换-u

#!/usr/bin/env PYTHONUNBUFFERED="YESSSSS" python

适用于RHEL 6.5。我相信env功能几乎是通用的。


5
FYI,这在 Debian 中不起作用。我不确定为什么它不起作用(查看ps输出没有任何区别),但它从来没有返回过。如果在 Debian 中这样做,很不清楚 Python 是否真的在运行。我尝试在几个地方使用了此方法 - 与等效命令行相比,明显不能正常工作。 - MartyMacGyver
@MartyMacGyver。很可能与您使用的env或甚至python版本有关。 - Mad Physicist
可能是环境版本的问题,但到目前为止它在任何现代Debian变体上都无法工作。在这种情况下,似乎Python并没有真正运行在Debian上,因此在某些平台和/或配置之外的使用受到限制。 - MartyMacGyver

-2

我最近为GNU Coreutils版本的env编写了一个补丁来解决这个问题:

http://lists.gnu.org/archive/html/coreutils/2017-05/msg00018.html

如果你有这个,你可以做到以下事情:
#!/usr/bin/env :lang:--foo:bar

env:lang:foo:--bar分成langfoo--bar字段。 它将在PATH中搜索解释器lang,然后使用参数--foobar以及脚本路径和该脚本的参数来调用它。

还有一个特性,可以在选项中间传递脚本的名称。假设您想运行lang -f <thecriptname> other-arg,然后是其余参数。 使用这个修补的env就像这样:

#!/usr/bin/env :lang:-f:{}:other-arg

最左边的字段相当于 {},将被替换为其后的第一个参数,在 hash bang 调用下,该参数是脚本名称。然后删除该参数。

在这里,other-arg 可能是由 lang 处理的内容,也可能是由脚本处理的内容。

要更好地理解,请参见补丁中众多的 echo 测试案例。

我选择了 : 字符,因为它是 POSIX 系统上用于 PATH 的现有分隔符。由于 env 进行 PATH 搜索,因此几乎不可能用于包含冒号的程序的名称。 {} 标记来自于 find 实用程序,它用于表示将路径插入到 -exec 命令行中。


2
对于任何看到这篇文章并感到有所期望的人,需要知道这个补丁已经被放弃了(http://lists.gnu.org/archive/html/coreutils/2017-05/msg00025.html)。 - Alex Miller

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接