人们为什么在Python脚本的第一行上编写#!/ usr / bin / env python shebang?


1046

在我看来,如果没有该行,文件运行相同。


1
下面的答案指出这只是一条注释行。并非总是如此。我有一个“你好,世界!” CGI脚本(.py)仅会运行并#!/usr/bin/env python在顶部显示该网页。
Chakotay


它们可以运行,但不能在预期的环境
尼古拉斯·汉密尔顿

18
我在7年中访问过此帖子很多次,因为有时我会忘记env hashbang。复制面食:)
BugHunterUK

Answers:


1082

如果您安装了多个版本的Python,请/usr/bin/env确保使用的解释器是您环境中的第一个解释器$PATH。另一种方法是对类似的东西进行硬编码#!/usr/bin/python;可以,但是不太灵活。

在Unix中,要解释的可执行文件可以通过#!在第一行的开头加上,然后是解释器(及其可能需要的任何标志)来指示要使用的解释器。

当然,如果您在谈论其他平台,则此规则不适用(但“ shebang行”没有害处,并且如果您将该脚本复制到具有 Unix基础的平台(例如Linux,Mac),将有帮助等)。


267
只是要添加:这适用于在Unix中通过将其变为可执行文件(chmod +x myscript.py),然后直接运行:./myscript.py而不是just来运行它python myscript.py
Craig McQueen 2010年

28
using env提供了最大的灵活性,因为用户可以通过更改PATH选择要使用的解释器。通常,这种灵活性不是必需的,但缺点是,例如linux不能使用脚本名称作为进程名称,而是ps恢复为“ python”。例如,当打包发行版的python应用时,我建议不要使用env
pixelbeat 2010年

9
py启动器可以在Windows上使用shebang行。它包含在Python 3.3中,也可以独立安装
jfs

6
一个重要的警告词,env的返回值最终会过期。如果您正在运行短期进程,那么这不太可能影响您。但是,/usr/bin/env: Key has expired许多小时后,我的进程已死于该消息。
malaverdiere

4
@malaverdiere您可以链接到任何说明此过期行为的资源吗?我找不到他们。
迈克尔

266

那就是shebang线。如Wikipedia条目所述

在计算中,shebang(也称为“ hashbang”,“ hashhpling”,“ bang bang”或“ crunchbang”)是指字符“#!”。当它们是解释器指令中的前两个字符作为文本文件的第一行时。在类似Unix的操作系统中,程序加载器将这两个字符的存在指示为文件是脚本,并尝试使用文件中第一行其余部分指定的解释器执行该脚本。

另请参见Unix FAQ条目

即使在Windows上,shebang行不能确定要运行的解释程序,也可以通过在shebang行上指定选项来将选项传递给解释程序。我发现在一次性脚本中保留通用的shebang行很有用(例如我在回答SO问题时编写的脚本),因此我可以在Windows和ArchLinux上快速对其进行测试。

使用env实用程序可以在路径上调用命令:

剩下的第一个参数指定要调用的程序名称;根据PATH环境变量进行搜索。任何剩余的参数将作为参数传递给该程序。


30
在Google上很容易找到-如果有人知道关键字(“ shebang行”是必不可少的)。
Arafangion 2010年

14
实际上,这种解释比我通过Google查看的其他参考文献更清楚。获得针对该问题的第一段说明总是更好,而不是阅读针对每种潜在用法的完整手册。
山姆·戈德堡

1
@Arafangion,您可能会发现此问题有用。TL; DR:symbolhound.com
ulidtko 2015年

@ulidtko:有趣的搜索引擎,请考虑写一个答案,以便约翰·加西亚斯的问题有一个更好的答案。
Arafangion 2015年

1
“即使在Windows上,shebang行不能确定要运行的解释器,也可以通过在shebang行上指定选项来将选项传递给解释器。” 那简直是错误的。如果发生这种情况,那是因为解释器本身正在处理shebang行。如果口译员对shebang行没有特殊的识别,那么就不会发生这种情况。Windows不与家当线做什么“你可以是描述在这种情况下是Python启动:python.org/dev/peps/pep-0397
卡兹

154

进一步扩展其他答案,这是一个小示例,说明了如何谨慎使用/usr/bin/envshebang行会导致命令行脚本出现问题:

$ /usr/local/bin/python -V
Python 2.6.4
$ /usr/bin/python -V
Python 2.5.1
$ cat my_script.py 
#!/usr/bin/env python
import json
print "hello, json"
$ PATH=/usr/local/bin:/usr/bin
$ ./my_script.py 
hello, json
$ PATH=/usr/bin:/usr/local/bin
$ ./my_script.py 
Traceback (most recent call last):
  File "./my_script.py", line 2, in <module>
    import json
ImportError: No module named json

json模块在Python 2.5中不存在。

防止此类问题的一种方法是使用大多数Python通常安装的版本化python命令名称:

$ cat my_script.py 
#!/usr/bin/env python2.6
import json
print "hello, json"

如果您只需要区分Python 2.x和Python 3.x,Python 3的最新版本还提供了一个python3名称:

$ cat my_script.py 
#!/usr/bin/env python3
import json
print("hello, json")

27
嗯,那不是我从那篇帖子中得到的。
格伦·杰克曼

1
本地与全球之间的差异。如果which python返回/usr/bin/python,则可以对本地目录路径进行硬编码:#!/usr/bin/python。但这没有#!/usr/bin/env python全局应用程序灵活。
noobninja '16

85

为了运行python脚本,我们需要告诉shell三件事:

  1. 该文件是一个脚本
  2. 我们要执行哪个解释器的脚本
  3. 口译员的路径

射手#!完成(1.)。shebang以a开头,#因为该#字符在许多脚本语言中都是注释标记。因此,解释器会自动忽略shebang行的内容。

env命令完成(2.)和(3.)。引用“草率”

env命令的常见用法是通过利用env将在$ PATH中搜索被告知要启动的命令的事实来启动解释器。由于shebang行需要指定绝对路径,并且由于各种解释器(perl,bash,python)的位置可能相差很大,因此通常使用:

#!/usr/bin/env perl  而不是尝试猜测它是/ bin / perl,/ usr / bin / perl,/ usr / local / bin / perl,/ usr / local / pkg / perl,/ fileserver / usr / bin / perl还是/ home用户系统上的/ MrDaniel / usr / bin / perl ...

另一方面,env几乎总是位于/ usr / bin / env中。(除非不是这种情况;某些系统可能使用/ bin / env,但这是一种相当罕见的情况,仅在非Linux系统上发生。)


1
“草率”在哪里?
Pacerier '17

44

也许您的问题是这样的:

如果要使用: $python myscript.py

您根本不需要那条线。系统将调用python,然后python解释器将运行您的脚本。

但是,如果您打算使用: $./myscript.py

像普通程序或bash脚本一样直接调用它,您需要编写该行以向系统指定运行该程序的程序(并使其通过可执行chmod 755


或者,您也可以编写python3 myscript.py
vijay shanker,2015年

44

execLinux内核的系统调用#!本身了解shebangs()

当您进行bash操作时:

./something

在Linux上,这会exec使用path 调用系统调用./something

内核的这一行在传递给exec以下文件的文件上调用:https : //github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25

if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))

它读取文件的头几个字节,并将其与进行比较#!

如果比较结果为真,那么Linux内核将解析其余的行,这将exec使用路径/usr/bin/env python和当前文件作为第一个参数进行另一个调用:

/usr/bin/env python /path/to/script.py

这适用于任何#用作注释字符的脚本语言。

是的,您可以使用以下方法进行无限循环:

printf '#!/a\n' | sudo tee /a
sudo chmod +x /a
/a

Bash识别错误:

-bash: /a: /a: bad interpreter: Too many levels of symbolic links

#! 只是碰巧是人类可读的,但这不是必需的。

如果文件以不同的字节开头,则exec系统调用将使用其他处理程序。另一个最重要的内置处理程序是用于ELF可执行文件:https : //github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305检查字节7f 45 4c 46(也恰好是人的)可读.ELF)。让我们通过读取的4个前字节来确认/bin/ls,这是ELF可执行文件:

head -c 4 "$(which ls)" | hd 

输出:

00000000  7f 45 4c 46                                       |.ELF|
00000004                                                                 

因此,当内核看到这些字节时,它将获取ELF文件,将其正确地放入内存,并使用它开始一个新进程。另请参阅:内核如何获取在Linux下运行的可执行二进制文件?

最后,您可以使用该binfmt_misc机制添加自己的shebang处理程序。例如,您可以.jarfiles添加自定义处理程序。该机制甚至通过文件扩展名支持处理程序。另一个应用程序是使用QEMU透明地运行不同体系结构的可执行文件

我不认为POSIX指定了shebangs:https ://unix.stackexchange.com/a/346214/32558 ,尽管它在基本原理部分中确实提到了,并且形式为“如果系统支持可执行脚本,则可能发生”。macOS和FreeBSD似乎也实现了它。

PATH 搜索动机

可能存在shebang的一个主要动机是,在Linux中,我们经常希望从以下命令运行命令PATH

basename-of-command

代替:

/full/path/to/basename-of-command

但是,如果没有shebang机制,Linux如何知道如何启动每种类型的文件?

在命令中对扩展进行硬编码:

 basename-of-command.py

或在每个解释器上实施PATH搜索:

python basename-of-command

这样做是有可能的,但这是一个主要问题,如果我们决定将命令重构为另一种语言,那么一切都会中断。

Shebangs很好地解决了这个问题。


39

从技术上讲,在Python中,这只是一条注释行。

仅当您从外壳程序(从命令行)运行py脚本时才使用此行。这就是众所周知的射帮!” ,它可用于各种情况,而不仅限于Python脚本。

在这里,它指示shell启动特定版本的Python(以照顾文件的其余部分。


shebang是Unix的概念。值得一提的是,如果您安装了Python启动器 ,它也可以在Windows上运行py.exe。这是标准Python安装的一部分。
florisla

38

这样做的主要原因是使脚本可跨操作系统环境移植。

例如在mingw下,python脚本使用:

#!/c/python3k/python 

在GNU / Linux发行版中,它是:

#!/usr/local/bin/python 

要么

#!/usr/bin/python

在所有最佳的商业Unix sw / hw系统(OS / X)下,它是:

#!/Applications/MacPython 2.5/python

或在FreeBSD上:

#!/usr/local/bin/python

但是,所有这些差异都可以通过使用以下命令使脚本可移植到所有人中:

#!/usr/bin/env python

2
在MacOSX下,它也是/usr/bin/python。在Linux下,几乎可以肯定系统安装的Python /usr/bin/python(我从没看过别的东西,这没有任何意义)。请注意,有些系统可能没有/usr/bin/env
阿尔伯特

1
如果您使用OSX并使用Homebrew并遵循其默认安装说明,则它将位于#!/ usr / local / bin / python下
2014年

@ Jean-PaulCalderone:请参阅下面的saaj答案。
pydsigner 2015年

2018年更新:Bare python不那么可移植,它是默认的Python解释器。Arch Linux长时间默认使用Python 3,发行版也在考虑这一问题,因为仅在2020
Python2。– mati865

22

强调大多数人错过的一件事可能是有道理的,这可能会阻止立即理解。当您输入python终端时,通常不会提供完整路径。而是在PATH环境变量中向上查找可执行文件。反过来,当您想直接执行Python程序时/path/to/app.py,必须告诉Shell使用什么解释器(通过hashbang,上面其他贡献者在解释什么)。

Hashbang希望有完整的口译员。因此,要直接运行Python程序,您必须提供Python二进制文件的完整路径,该路径有很大差异,尤其是考虑到使用virtualenv时。为了解决可移植性,/usr/bin/env使用了技巧。后者最初旨在就地更改环境并在其中运行命令。如果未提供任何更改,它将在当前环境中运行该命令,从而有效地导致执行该操作的相同PATH查找。

来自unix stackexchange的来源


14

这是一个Shell约定,它告诉Shell哪个程序可以执行脚本。

#!/ usr / bin / env python

解析为Python二进制文件的路径。



9

您可以使用virtualenv尝试此问题

这是test.py

#! /usr/bin/env python
import sys
print(sys.version)

创建虚拟环境

virtualenv test2.6 -p /usr/bin/python2.6
virtualenv test2.7 -p /usr/bin/python2.7

激活每个环境,然后检查差异

echo $PATH
./test.py

9

它只是指定您要使用的解释器。要理解这一点,可以通过在终端上创建一个文件touch test.py,然后在该文件中键入以下内容:

#!/usr/bin/env python3
print "test"

chmod +x test.py使你的脚本执行。之后,当您执行此操作时,./test.py将出现错误消息:

  File "./test.py", line 2
    print "test"
               ^
SyntaxError: Missing parentheses in call to 'print'

因为python3不支持print运算符。

现在继续并将代码的第一行更改为:

#!/usr/bin/env python2

并且可以正常工作,并打印test到stdout,因为python2支持print运算符。因此,现在您已经了解了如何在脚本解释器之间切换。


9

在我看来,如果没有该行,文件运行相同。

如果是这样,那么也许您正在Windows上运行Python程序?Windows不使用该行,而是使用文件扩展名来运行与文件扩展名关联的程序。

但是在2011年,开发了“ Python启动器”,在某种程度上模仿了Windows的Linux行为。仅限于选择运行哪个Python解释器-例如,在同时安装了Python 2和Python 3的系统上进行选择。启动器可以选择py.exe通过Python安装进行安装,并且可以与.py文件关联,以便启动器将检查该行,然后启动指定的Python解释器版本。


6
他可能也正在使用$ python myscript.py
思南Ünür

我由于没有线路而使用python script.py犯了一个错误,有一天,我只是做了./myscript.py而一切都停止了工作,然后意识到系统将文件视为shell脚本而不是python脚本。
瓜瓜

8

这意味着更多的历史信息,而不是“真实的”答案。

请记住,在过去,您有很多像操作系统一样的unix操作系统,其设计人员都对放置内容有自己的看法,有时甚至根本不包括Python,Perl,Bash或许多其他GNU /开源内容

甚至在不同的Linux发行版中也是如此。在Linux上-FHS之前的版本[1]-您可能在/ usr / bin /或/ usr / local / bin /中有python。或者它可能尚未安装,所以您构建了自己的并将其放入〜/ bin

Solaris是我曾经从事过的最糟糕的工作,部分是从Berkeley Unix到System V的过渡。您可能会在/ usr /,/ usr / local /,/ usr / ucb,/ opt /等目录中找到内容。对于一些真的长的路。我对Sunfreeware.com中的内容有记忆,但每个软件包都安装在其自己的目录中,但是我记不起来是否将二进制文件链接到/ usr / bin。

哦,有时/ usr / bin在NFS服务器上[2]。

因此,env开发实用程序来解决此问题。

然后,你可以写#!/bin/env interpreter只要路径是正确的事情有一个合理的运行的机会。当然,合理的意思是(对于Python和Perl)您还设置了适当的环境变量。对于bash / ksh / zsh来说,它才有效。

这很重要,因为人们正在传递shell脚本(例如perl和python),并且如果您在Red Hat Linux工作站上对/ usr / bin / python进行硬编码,那么在SGI上会很糟糕...嗯,不,我认为IRIX将python放在了正确的位置。但是在Sparc工作站上,它可能根本不运行。

我想念我的sparc站。但不是很多。好的,现在您已经让我在E-Bay上四处走动。卑鄙的人

[1]文件系统层次结构标准。https://zh.wikipedia.org/wiki/Filesystem_Hierarchy_Standard

[2]是的,有时人们仍然会做类似的事情。不,我没有在皮带上戴萝卜或洋葱。


5

如果您是在虚拟环境中运行脚本,请说venv,然后在执行which python时执行venv将显示Python解释器的路径:

~/Envs/venv/bin/python

请注意,虚拟环境名称嵌入在Python解释器的路径中。因此,在脚本中对此路径进行硬编码将导致两个问题:

  • 如果将脚本上载到存储库,则将强制其他用户使用相同的虚拟环境名称。这是他们首先发现问题的方法。
  • 将无法运行在多个虚拟环境中的脚本,即使你有在其他虚拟环境中所有需要的软件包。

因此,要补充Jonathan的答案,理想的shebang是#!/usr/bin/env python,不仅是跨OS的可移植性,而且是跨虚拟环境的可移植性!


3

考虑到python2和之间的可移植性问题python3,除非您的程序与两者兼容,否则应始终指定任何一个版本。

一些分布航运python符号链接到python3现在一段时间-不要依赖pythonpython2

PEP 394强调了这一点:

为了容忍平台之间的差异,所有需要调用Python解释器的新代码都不应指定python,而应指定python2或python3(或更具体的python2.x和python3.x版本;请参阅迁移说明) 。从shell脚本调用时,通过system()调用调用时或在任何其他上下文中调用时,都应在shebang中进行区分。


2

当您有多个python版本时,它会告诉解释器与哪个版本的python一起运行程序。


0

它允许您选择要使用的可执行文件。如果您可能安装了多个python,并且每个安装中都有不同的模块并希望选择,则这非常方便。例如

#!/bin/sh
#
# Choose the python we need. Explanation:
# a) '''\' translates to \ in shell, and starts a python multi-line string
# b) "" strings are treated as string concat by python, shell ignores them
# c) "true" command ignores its arguments
# c) exit before the ending ''' so the shell reads no further
# d) reset set docstrings to ignore the multiline comment code
#
"true" '''\'
PREFERRED_PYTHON=/Library/Frameworks/Python.framework/Versions/2.7/bin/python
ALTERNATIVE_PYTHON=/Library/Frameworks/Python.framework/Versions/3.6/bin/python3
FALLBACK_PYTHON=python3

if [ -x $PREFERRED_PYTHON ]; then
    echo Using preferred python $ALTERNATIVE_PYTHON
    exec $PREFERRED_PYTHON "$0" "$@"
elif [ -x $ALTERNATIVE_PYTHON ]; then
    echo Using alternative python $ALTERNATIVE_PYTHON
    exec $ALTERNATIVE_PYTHON "$0" "$@"
else
    echo Using fallback python $FALLBACK_PYTHON
    exec python3 "$0" "$@"
fi
exit 127
'''

__doc__ = """What this file does"""
print(__doc__)
import platform
print(platform.python_version())

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.