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

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

答案

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

在 Unix 中,要解释的可执行文件可以通过#!来指示要使用的解释器#!在第一行的开头,接着是解释器(及其可能需要的所有标志)。

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

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

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

另请参见Unix FAQ 条目

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

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

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

进一步扩展其他答案,这是一个小示例,说明了如何谨慎使用/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

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")

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

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

社 bang #!完成(1.)。 shebang 以#开头,因为#字符是许多脚本语言中的注释标记。因此,解释器会自动忽略 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 系统上发生。)

也许您的问题是这样的:

如果要使用: $python myscript.py

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

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

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

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

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

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

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

例如在 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

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 内核将解析其余的行,这将使用路径/usr/bin/env python和当前文件作为第一个参数进行另一个exec调用:

/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是人类可读的)。让我们通过读取/bin/ls的前四个字节来确认,这是一个 ELF 可执行文件:

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

输出:

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

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

最后,您可以使用binfmt_misc机制添加自己的 shebang 处理程序。例如,您可以.jar文件添加自定义处理程序 。该机制甚至通过文件扩展名支持处理程序。另一个应用程序是使用 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 很好地解决了这个问题。

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

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

来自 unix stackexchange 的来源

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

#!/usr/bin/env python

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