ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

下面的答案表明它只是一个注释行。情况并非总是如此。我有一个“你好,世界!” CGI 脚本 (.py) 只会运行并显示顶部带有 #!/usr/bin/env python 的网页。
它们可以运行,但不在预期的环境中
这条线在virtualenv中的作用是什么?假设我的虚拟环境使用 3.7.7 而 python 全局有 2.7(这是我在虚拟环境之外使用 python -V 时得到的),当我在虚拟环境中玩 shabanged 文件时,它是否指的是 python2.7来自全球的口译员?
我已经从标题中删除了“shebang”,因为它最初并不存在,并且它添加到标题中会使整个问题及其答案变得荒谬(“Q:为什么要添加 shebang?” - “A:这被称为 shebang “ … 不)。

n
nbro

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

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

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


补充一点:这适用于您在 Unix 中通过使其可执行 (chmod +x myscript.py) 然后直接运行它来运行它:./myscript.py,而不仅仅是 python myscript.py
使用 env 提供了最大的灵活性,因为用户可以通过更改 PATH 来选择要使用的解释器。但是通常不需要这种灵活性,缺点是例如 linux 不能使用脚本名称作为 ps 中的进程名称并恢复为“python”。例如,在为发行版打包 python 应用程序时,我建议不要使用 env
py launcher 可以在 Windows 上使用 shebang 行。它包含在 Python 3.3 或 it can be installed independently 中。
一个重要的警告,env 的返回值最终会过期。如果您正在运行短暂的进程,这不太可能影响您。但是,几个小时后,我的进程因消息 /usr/bin/env: Key has expired 而死亡。
@malaverdiere 你能链接到解释这种过期行为的任何资源吗?我找不到他们。
S
Sinan Ünür

这称为 shebang line。作为 Wikipedia entry explains

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

另请参阅 Unix FAQ entry

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

env utility 允许您在路径上调用命令:

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


@Arafangion 您可能会发现 this question 很有用。 TL;DR:symbolhound.com
“即使在 Windows 上,shebang 行无法确定要运行的解释器,您也可以通过在 shebang 行上指定选项来将选项传递给解释器。”这完全是错误的;如果发生这种情况,那是因为解释器本身正在处理 shebang 行。如果解释器对 shebang 行没有特别的识别,那么就不会发生这种情况。 Windows 不会对 shebang 行做任何事情。”在这种情况下,您可能描述的是 python 启动器:python.org/dev/peps/pep-0397
Windows 根本没有规定使“.py”文件可执行。通过将 .py 后缀作为文档关联到应用程序,Python 文件在 Explorer shell 中显示为可执行文件。如果该应用程序是特定于 Python 的 plauncher,那么您将获得 hash bang 处理。而已。
@Shuzheng 请仔细阅读句子。它既不是你或 Kaz 认为它所说的。例如,Windows 上的 perl 并不关心有没有 /usr/bin/perl,但会注意传递给它的选项。
@Shuzheng_请仔细阅读句子。它既不是你或 Kaz 认为它所说的。_
N
Ned Deily

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

$ /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

Python 2.5 中不存在 json 模块。

防止此类问题的一种方法是使用通常随大多数 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")

嗯,这不是我从那个帖子中得到的。
局部和全局的区别。如果 which python 返回 /usr/bin/python,则可以对本地目录路径进行硬编码:#!/usr/bin/python。但这不如具有全局应用程序的 #!/usr/bin/env python 灵活。
f
fuzzydrawrings

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

该文件是一个脚本 我们要执行脚本的解释器 所述解释器的路径

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

env 命令完成 (2.) 和 (3.)。至quote "grawity,"

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 系统上。)


“引力”在哪里?
C
Ciro Santilli Путлер Капут 六四事

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

当你在 bash 上做:

./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 内核解析,然后再调用一次 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 /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,它也恰好是人类可读的)。让我们通过读取 /bin/ls 的前 4 个字节来确认,这是一个 ELF 可执行文件:

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

输出:

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

因此,当内核看到这些字节时,它会获取 ELF 文件,将其正确放入内存,并使用它启动一个新进程。另请参阅:How does kernel get an executable binary file running under linux?

最后,您可以使用 binfmt_misc 机制添加自己的 shebang 处理程序。例如,您可以添加 custom handler for .jar files。这种机制甚至支持文件扩展名的处理程序。另一个应用程序是 transparently run executables of a different architecture with QEMU

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

PATH 搜索动机

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

basename-of-command

代替:

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

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

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

 basename-of-command.py

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

python basename-of-command

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

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

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

您应该使用 #!/usr/bin/env python 而不仅仅是 /usr/bin/python 的一个主要用例是带有 pyenv 的版本管理器。

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

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

/home/ciro/.pyenv/shims/python

因此与 /usr/bin/python 相去甚远,某些系统可能会通过 update-alternatives symlinks 处理。


O
Obscure Geek

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

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

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

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

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


或者你可以写 python3 myscript.py
J
Jonathan Cline IEEE

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

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

#!/c/python3k/python 

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

#!/usr/local/bin/python 

或者

#!/usr/bin/python

在最好的商业 Unix 软件/硬件系统 (OS/X) 下,它是:

#!/Applications/MacPython 2.5/python

或在 FreeBSD 上:

#!/usr/local/bin/python

然而,所有这些差异都可以通过使用以下方式使脚本在所有环境中可移植:

#!/usr/bin/env python

在 MacOSX 下,它也是 /usr/bin/python。在Linux下,系统安装的Python也几乎可以肯定是/usr/bin/python(我没见过其他的,没有任何意义)。请注意,有些系统可能没有 /usr/bin/env
如果您在 OSX 上并使用 Homebrew 并遵循其默认安装说明,它将位于 #!/usr/local/bin/python 下
2018 年更新:Bare python 不是那么便携,它是分发默认的 Python 解释器。 Arch Linux 长期以来默认使用 Python 3,并且可能发行版也在考虑它,因为 Python 2 仅支持到 2020 年。
n
nbro

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

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

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


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

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

Hashbang 需要解释器的完整路径。因此,要直接运行您的 Python 程序,您必须提供差异很大的 Python 二进制文件的完整路径,尤其是考虑到使用 virtualenv。为了解决可移植性,使用了 /usr/bin/env 的技巧。后者最初旨在就地改变环境并在其中运行命令。如果没有提供任何更改,它会在当前环境中运行命令,这实际上会导致相同的 PATH 查找,这可以解决问题。

Source from unix stackexchange


F
Frank Krueger

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

#!/usr/bin/env python

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


G
Grzegorz Wierzowiecki

这是推荐的方式,在文档中提出:

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


P
Pavel

它只是指定您要使用的解释器。要理解这一点,请通过执行 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 不支持打印操作符。

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

#!/usr/bin/env python2

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


S
Sercan

您可以使用 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

C
Craig McQueen

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

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

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


他可能也在使用 $ python myscript.py
我犯了一个错误,因为没有该行并使用了 python script.py,有一天我刚刚做了 ./myscript.py,一切都停止了工作,然后意识到系统正在将该文件视为 shell 脚本而不是 python 脚本。
P
Petro

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

请记住,在过去,你有很多类似 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/ 等中找到一些东西。这可能会使对于一些非常长的路径。我记得 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_Standard

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


C
Community

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

~/Envs/venv/bin/python

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

如果您将脚本上传到存储库,则会强制其他用户使用相同的虚拟环境名称。这是如果他们首先发现问题。

即使您在其他虚拟环境中拥有所有必需的包,您也无法在多个虚拟环境中运行该脚本。

因此,为了补充 Jonathan 的答案,理想的 shebang 是 #!/usr/bin/env python,不仅是为了跨操作系统的可移植性,还为了跨虚拟环境的可移植性!


R
Roshin Raphel

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

#!/bin/bash/python3
print("Hello World")

是一个 python3 脚本,和 b.py :

#!/bin/bash/python
print "Hello World"

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

chmod +x a.py

/bin/bash/python?这让我很困惑。
@KevinC 这是python解释器的实际路径。r
也许使用:#!/usr/bin/env python3。这样,系统会搜索它的PATH找到python3,这是一个更漂亮的方法。
Z
Zulan

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

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

PEP 394 强调了这一点:

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


K
Katie T

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


G
Goblinhack

它允许您选择要使用的可执行文件;如果您可能安装了多个 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())

V
Veemo

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

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


B
Bhargav Rao

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

#! /usr/bin/env python