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

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

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


当前回答

Linux内核的exec系统调用可以原生地理解shebangs (#!

当你在bash上执行时:

./something

在Linux上,它调用exec系统调用,路径为。/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

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

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

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

她完美地解决了这个问题。

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

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

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

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

/home/ciro/.pyenv/shims/python

所以与/usr/bin/python完全不同,有些系统可能会通过更新替代符号链接来处理。

其他回答

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

Hashbang期望到解释器的完整路径。因此,要直接运行你的Python程序,你必须提供Python二进制文件的完整路径,这差异很大,特别是考虑到使用virtualenv。为了解决可移植性,使用了/usr/bin/env的技巧。后者最初的目的是就地改变环境并在其中运行命令。当没有提供任何更改时,它将在当前环境中运行该命令,这将有效地导致相同的PATH查找。

源代码来自unix stackexchange

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

请记住,过去有很多类似unix的操作系统,它们的设计者都有自己的想法,把东西放在哪里,有时根本不包括Python、Perl、Bash或许多其他GNU/开源的东西。

这甚至适用于不同的Linux发行版。在Linux - pre-FHS1 -你可能在/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解释器,只要路径是正确的,程序就有合理的运行机会。当然,合理意味着(对于Python和Perl)还设置了适当的环境变量。对于bash/ksh/zsh,它可以正常工作。

这很重要,因为人们在传递shell脚本(如Perl和Python),如果你在Red Hat Linux工作站上硬编码/usr/bin/python,它将在SGI上坏掉……不,我认为IRIX把Python放在了正确的位置。但在Sparc空间站上,它可能根本无法运行。

我想念我的斯巴达站。但不是很多。好吧,现在你让我在易趣网上搜了。Bastages。


1文件系统层次标准。

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

这被称为shebang线。维基百科词条解释道:

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

请参见Unix FAQ条目。

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

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

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

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

在Unix中,要解释的可执行文件可以通过#!在第一行的开头,后面跟着解释器(以及它可能需要的任何标志)。

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

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