为什么人们在Python脚本的第一行写#! /usr/bin/envpython?

我在Python文件的顶部看到这些:

#!/usr/bin/env python
#!/usr/bin/env python3

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

955031 次浏览

这被称为舍帮线。作为维基百科条目解释

在计算中,当解释器指令中的前两个字符作为文本文件的第一行时,sheang(也称为哈希邦、散列、磅邦或crunchang)引用字符“#!”。在类Unix操作系统中,程序加载器将这两个字符的存在视为文件是脚本的指示,并尝试使用文件中第一行其余部分指定的解释器执行该脚本。

另见Unix FAQ条目

即使在Windows上,sheang行不能决定要运行的解释器,您也可以通过在sheang行上指定它们来将选项传递给解释器。我发现在一次性脚本中保留通用的sheang行很有用(例如我在SO上回答问题时编写的脚本),这样我就可以在Windows和Archlinux上快速测试它们。

环境实用程序允许您在路径上调用命令:

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

如果您安装了多个版本的Python,/usr/bin/env将确保使用的解释器是您环境$PATH上的第一个解释器。另一种选择是硬编码类似#!/usr/bin/python;没关系,但不太灵活。

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

如果你谈论的是其他平台,当然,这条规则不适用(但是“sheband line”没有坏处,如果你将该脚本复制到Unix基础的平台,例如Linux,Mac等,将会有所帮助)。

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

#!/usr/bin/env python

resolves to a path to the Python binary.

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

此行仅在您运行py脚本从壳(从命令行)时使用。这被称为"谢邦!",它用于各种情况,而不仅仅是Python脚本。

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

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

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

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

扩展一下其他答案,这里有一个小例子,说明您的命令行脚本如何因不谨慎地使用/usr/bin/env sheband行而陷入困境:

$ /usr/local/bin/python -VPython 2.6.4$ /usr/bin/python -VPython 2.5.1$ cat my_script.py#!/usr/bin/env pythonimport jsonprint "hello, json"$ PATH=/usr/local/bin:/usr/bin$ ./my_script.pyhello, json$ PATH=/usr/bin:/usr/local/bin$ ./my_script.pyTraceback (most recent call last):File "./my_script.py", line 2, in <module>import jsonImportError: No module named json

json模块在Python 2.5中不存在。

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

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

如果您只需要区分Python 2. x和Python 3. x,最近发布的Python 3还提供了python3名称:

$ cat my_script.py#!/usr/bin/env python3import jsonprint("hello, json")

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

例如在ming w下,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.2.2。可执行的Python脚本

在BSD'ish Unix系统上,可以直接制作Python脚本可执行文件,如shell脚本,通过将行

#! /usr/bin/env python3.2

http://docs.python.org/py3k/tutorial/interpreter.html#executable-python-scripts

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

  1. 文件是一个脚本
  2. 我们希望哪个解释器执行脚本
  3. 翻译的路径

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

env命令完成(2.)和(3.)。到引用"Grawity"

env命令的一个常见用法是启动解释器,通过使用env将搜索$PATH以获取它被告知的命令这一事实要发射。由于Sheband线需要一个绝对路径指定,并且由于各种解释器的位置(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系统)

您可以尝试使用虚拟环境来解决此问题

这是test.py

#! /usr/bin/env pythonimport sysprint(sys.version)

创建虚拟环境

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

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

echo $PATH./test.py

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

哈希邦期待完整路径到解释器。因此,要直接运行您的Python程序,您必须提供Python二进制文件的完整路径,该路径变化很大,特别是考虑到使用虚拟环境。为了解决可移植性,使用了/usr/bin/env的技巧。后者最初旨在就地更改环境并在其中运行命令。当没有提供任何更改时,它会在当前环境中运行命令,这实际上会导致相同的PATH查找。

来自unix stackExchange的源代码

也许你的问题是在这个意义上:

如果你想使用:$python myscript.py

你根本不需要那行。系统会调用python,然后python解释器会运行你的脚本。

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

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

这告诉脚本python目录在哪里!

#! /usr/bin/env python

如果您在虚拟环境中运行脚本,例如venv,那么在处理venv时执行which python将显示Python解释器的路径:

~/Envs/venv/bin/python

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

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

因此,为了增加乔纳森的答案,理想的sheband是#0,不仅可以跨操作系统移植,还可以跨虚拟环境移植!

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

一些发行版现在将python符号链接到python3一段时间-不依赖于pythonpython2

PEP 394强调了这一点:

为了容忍跨平台的差异,所有新代码需要调用Python解释器不应指定python,而是而是应该指定python2或python3(或更具体的python2. x和python3. x版本;见迁移笔记)。这从shell调用时,应在sheBangs中进行区分脚本,当通过system()调用时,或在任何其他内容

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

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

#!/usr/bin/env python3print "test"

并执行chmod +x test.py以使您的脚本可执行。之后,当您执行./test.py时,您应该会收到一个错误:

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

因为python3不支持print运算符。

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

#!/usr/bin/env python2

它可以工作,将test打印到stdout,因为python2支持打印运算符。所以,现在您已经学习了如何在脚本解释器之间切换。

Linux内核的exec系统调用原生理解sheBangs(#!

当你使用bash时:

./something

在Linux,这调用路径./somethingexec系统调用。

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

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

它读取文件的第一个字节,并将它们与#!进行比较。

如果比较为true,则该行的其余部分由Linux内核解析,它使用以下方式进行另一个exec调用:

  • 可执行文件:/usr/bin/env
  • 第一个参数:python
  • 第二个参数:脚本路径

因此相当于:

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

env是一个可执行文件,搜索PATH以查找/usr/bin/python,然后最后调用:

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

Python解释器确实在文件中看到#!行,但#是Python中的注释字符,因此该行只是作为常规注释被忽略。

是的,你可以做一个无限循环:

printf '#!/a\n' | sudo tee /asudo 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也恰好是人类可读的)。让我们通过阅读/bin/ls的前4个字节来确认这一点,它是一个ELF可执行文件:

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

输出:

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

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

最后,您可以使用binfmt_misc机制添加自己的sheband处理程序。例如,您可以添加#1文件的自定义处理程序。这种机制甚至支持按文件扩展名的处理程序。另一个应用程序是使用QEMU透明地运行不同架构的可执行文件

我不认为POSIX指定了sheBangs:https://unix.stackexchange.com/a/346214/32558,尽管它确实在基本原理部分提到了,并且以“如果系统支持可执行脚本,则可能会发生某些事情”的形式出现。

PATH搜索动机

可能的是,sheBangs存在的一个很大的动机是,在Linux,我们经常希望从PATH运行命令,就像:

basename-of-command

而不是:

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

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

在命令中硬编码扩展:

 basename-of-command.py

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

python basename-of-command

这是一种可能性,但这有一个主要问题,如果我们决定将命令重构为另一种语言,一切都会崩溃。

谢邦斯完美地解决了这个问题。

env的主要用例:pyenv和其他版本管理器

为什么你应该使用#!/usr/bin/env python而不是/usr/bin/python的一个主要用例是pyenv的版本管理器。

pyenv允许您在一台机器上轻松安装多个python版本,以便能够在没有虚拟化的情况下更好地复制其他项目。

然后,它通过在PATH中设置其顺序来管理“当前”python版本:例如,如apt-get安装不同的python版本所示,pyenv管理的python可以位于:

/home/ciro/.pyenv/shims/python

所以没有接近/usr/bin/python的地方,有些系统可能会通过#1符号链接处理。

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

请记住,在那一天,你有很多像unix这样的操作系统,它们的设计者都有自己的想法来放置东西,有时不包括Python、Perl、Bash或许多其他GNU/开源的东西在所有

不同的Linux发行版也是如此。在Linux--pre-FHS[1]-您可能在 /usr/bin/或 /usr/local/bin/.或者它可能尚未安装,所以您构建了自己的并将其放入~/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 HatLinux工作站上硬编码 /usr/bin/python,它将在SGI上崩溃……好吧,不,我认为IRIX把python放在了正确的位置。但在Sparc站上,它可能根本无法运行。

我想念我的spac电台。但不是很多。好吧,现在你让我在e-Bay上闲逛。混蛋。

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

[2]是的,有时人们仍然会这样做。不,我没有在腰带上穿萝卜或洋葱。

它允许您选择要使用的可执行文件;这非常如果您有多个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/pythonALTERNATIVE_PYTHON=/Library/Frameworks/Python.framework/Versions/3.6/bin/python3FALLBACK_PYTHON=python3
if [ -x $PREFERRED_PYTHON ]; thenecho Using preferred python $ALTERNATIVE_PYTHONexec $PREFERRED_PYTHON "$0" "$@"elif [ -x $ALTERNATIVE_PYTHON ]; thenecho Using alternative python $ALTERNATIVE_PYTHONexec $ALTERNATIVE_PYTHON "$0" "$@"elseecho Using fallback python $FALLBACK_PYTHONexec python3 "$0" "$@"fiexit 127'''
__doc__ = """What this file does"""print(__doc__)import platformprint(platform.python_version())

#!/bin/bash/python3#!/bin/bash/python行指定要使用的python编译器。您可能安装了多个python版本。例如,
a.py:

#!/bin/bash/python3print("Hello World")

是python3脚本,
b.py:

#!/bin/bash/pythonprint "Hello World"

是一个python 2. x脚本
为了运行这个文件./a.py./b.py被使用,您需要事先给予文件执行权限,否则执行将导致Permission denied错误。
对于执行权限,

chmod +x a.py

当您执行python文件时,您可以使用./file.py,其中file是文件的名称。 /usr/bin/env是PATH,然后python是python 2,python3是python 3(duh)

#!/usr/bin/env python也可以允许python文件被其他程序执行,只要你使用chmod +x file.py