关于shell:为什么人们要写#!/usr/bin/env在python脚本的第一行?

Why do people write the #!/usr/bin/env python shebang on the first line of a Python script?

在我看来,没有这一行文件运行的方式是一样的。


如果安装了多个版本的python,那么/usr/bin/env将确保所使用的解释器是环境的$PATH上的第一个解释器。另一种选择是硬编码类似于#!/usr/bin/python的东西;没关系,但灵活性较低。

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

当然,如果您谈论的是其他平台,则此规则不适用(但"shebang行"不会造成任何伤害,而且如果您将该脚本复制到具有Unix基础的平台(如Linux、Mac等),则会有所帮助)。


这就是所谓的shebang线。正如维基百科的条目所解释的那样:

In computing, a shebang (also called a hashbang, hashpling, pound bang, or crunchbang) refers to the characters"#!" when they are the first two characters in an interpreter directive as the first line of a text file. In a Unix-like operating system, the program loader takes the presence of these two characters as an indication that the file is a script, and tries to execute that script using the interpreter specified by the rest of the first line in the file.

另请参见Unix FAQ条目。

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

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

The first remaining argument specifies the program name to invoke; it is searched for according to the PATH environment variable. Any remaining arguments are passed as arguments to that program.


在其他答案上做一点扩展,下面是一个小例子,说明如何通过不小心使用/usr/bin/envshebang行而使命令行脚本陷入麻烦:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ /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

Python2.5中不存在JSON模块。

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

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

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

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


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

  • 文件是一个脚本
  • 我们要执行脚本的解释器
  • 所述译员的路径
  • shebang #!完成(1)。shebang以#开头,因为#字符是许多脚本语言中的注释标记。因此,解释器会自动忽略shebang行的内容。

    env命令完成(2)和(3)。引用"格雷维蒂",

    A common use of the env command is to launch interpreters, by making
    use of the fact that env will search $PATH for the command it is told
    to launch. Since the shebang line requires an absolute path to be
    specified, and since the location of various interpreters (perl, bash,
    python) may vary a lot, it is common to use:

    #!/usr/bin/env perl  instead of trying to guess whether it is
    /bin/perl, /usr/bin/perl, /usr/local/bin/perl, /usr/local/pkg/perl,
    /fileserver/usr/bin/perl, or /home/MrDaniel/usr/bin/perl on the user's
    system...

    On the other hand, env is almost always in /usr/bin/env. (Except in
    cases when it isn't; some systems might use /bin/env, but that's a
    fairly rare occassion and only happens on non-Linux systems.)


    也许你的问题是这样的:

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

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

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

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


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

    仅当从shell(从命令行)运行py脚本时才使用此行。这就是所谓的"shebang!"并且它在各种情况下使用,而不仅仅是与Python脚本一起使用。

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


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

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

    1
    #!/c/python3k/python

    在gnu/linux发行版下,它可以是:

    1
    #!/usr/local/bin/python

    1
    #!/usr/bin/python

    在最好的商用Unix sw/hw系统(OS/X)下,它是:

    1
    #!/Applications/MacPython 2.5/python

    或者在FreeBSD上:

    1
    #!/usr/local/bin/python

    但是,所有这些差异都可以通过以下方式使脚本在所有方面都可移植:

    1
    #!/usr/bin/env python


    Linux内核的exec系统调用本机理解shebangs(#!)

    当你在巴什上做的时候:

    1
    ./something

    在Linux上,它使用路径./something调用exec系统调用。

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

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

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

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

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

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

    是的,你可以用以下方法做一个无限循环:

    1
    2
    3
    4
    printf '#!/a
    '
    | sudo tee /a
    sudo chmod +x /a
    /a

    bash识别错误:

    1
    -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)。这将读取ELF文件,将其正确地放入内存,并用它启动一个新的进程。另请参见:内核如何获得在Linux下运行的可执行二进制文件?

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

    但是,我不认为posix指定了shebangs:https://unix.stackexchange.com/a/346214/32558,尽管它在基本原理部分中提到过,并且形式为"如果系统支持可执行脚本,则可能会发生一些事情"。然而,MacOS和FreeBSD似乎也在实施它。


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

    hashbang需要一个解释器的完整路径。因此,要直接运行您的python程序,您必须提供到python二进制文件的完整路径,这个路径变化很大,特别是考虑到virtualenv的使用。为了解决可移植性问题,使用了/usr/bin/env的技巧。后者最初的目的是在适当的地方改变环境并在其中运行一个命令。如果不提供任何更改,它将在当前环境中运行命令,这将有效地导致执行该技巧的同一个PATH查找。

    来自Unix StackExchange的源


    建议的方法,在文件中提出:

    2.2.2. Executable Python Scripts

    On BSD’ish Unix systems, Python scripts can be made directly
    executable, like shell scripts, by putting the line

    1
    #! /usr/bin/env python3.2

    来自http://docs.python.org/py3k/tutorial/explorer.html可执行的python脚本


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

    1
    #!/usr/bin/env python

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


    您可以使用virtualenv尝试此问题

    这里是Test.Py

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

    创建虚拟环境

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

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

    1
    2
    echo $PATH
    ./test.py

    It seems to me like the files run the same without that line.

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

    但是在2011年,开发了一个"python启动程序"(在某种程度上)来模仿Windows的Linux行为。这仅限于选择运行哪一个python解释器—例如,在安装了这两个解释器的系统上选择python 2和python 3。启动程序可以通过python安装作为py.exe安装,并且可以与.py文件关联,以便启动程序检查该行,然后启动指定的python解释器版本。


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

    请记住,以前有很多类Unix的操作系统,它们的设计者都有自己的概念,可以把东西放在哪里,有时甚至根本不包括python、perl、bash或其他许多GNU/开源的东西。

    对于不同的Linux发行版来说,这甚至是正确的。在Linux上--pre-fhs[1]-您可以在/usr/bin/或/usr/local/bin/中使用python。或者它可能没有安装,所以您构建了自己的并将其放入~/bin

    Solaris是我工作过的最糟糕的版本,部分原因是从Berkeley Unix到System V的过渡。你可以在/usr/、/usr/local/、/usr/ucb、/opt/etc中找到一些东西。这可能会导致一些非常长的路径。我对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://en.wikipedia.org/wiki/filesystem_hierarchy_标准

    [2]是的,有时人们仍然会做这样的事情。不,我的腰带上既没有萝卜也没有洋葱。


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

    ~/Envs/venv/bin/python

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

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

    因此,除乔纳森的回答外,理想的shebang是#!/usr/bin/env python,不仅是用于跨OSE的可移植性,还用于跨虚拟环境的可移植性!


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

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

    并执行EDOCX1[1]以使脚本可执行。在这之后,当您执行./test.py操作时,您会得到一个错误消息,说:

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

    因为python3不支持print运算符。

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

    1
    #!/usr/bin/env python2

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


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

    一些分配暂时与python3符号相连,但暂时不依赖python作为python2

    PEP 394强调了这一点:

    In order to tolerate differences across platforms, all new code that
    needs to invoke the Python interpreter should not specify python, but
    rather should specify either python2 or python3 (or the more specific
    python2.x and python3.x versions; see the Migration Notes). This
    distinction should be made in shebangs, when invoking from a shell
    script, when invoking via the system() call, or when invoking in any
    other context.


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


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

    1
    #! /usr/bin/env python