为什么人们在 Python 脚本的第一行写“#!/usr/bin/env python”?
- 2024-11-26 08:37:00
- admin 原创
- 144
问题描述:
我在 Python 文件的顶部看到了这些内容:
#!/usr/bin/env python
#!/usr/bin/env python3
在我看来,即使没有那一行,文件运行起来也是一样的。
解决方案 1:
如果您安装了多个版本的 Python,/usr/bin/env
将确保使用的解释器是您环境中的第一个解释器$PATH
。另一种方法是硬编码类似的东西#!/usr/bin/python
;这没问题,但灵活性较差。
在 Unix 中,需要解释的可执行#!
文件可以通过在第一行开头加上 来指示要使用的解释器,后面跟着解释器(以及它可能需要的任何标志)。
当然,如果您谈论的是其他平台,则此规则不适用(但“shebang line”不会造成任何危害,而且如果您将该脚本复制到具有Unix 基础的平台(例如 Linux、Mac 等),它会有所帮助)。
解决方案 2:
这就是所谓的shebang line。正如Wikipedia 条目所解释的那样:
在计算中,shebang(也称为 hashbang、hashpling、pound bang 或 crunchbang)是指当解释器指令中的前两个字符“#!”作为文本文件的第一行时。在类 Unix 操作系统中,程序加载器将这两个字符的存在视为该文件是脚本的指示,并尝试使用文件第一行其余部分指定的解释器来执行该脚本。
另请参阅Unix FAQ 条目。
即使在 Windows 上,shebang 行无法确定要运行的解释器,您也可以通过在 shebang 行上指定选项来将选项传递给解释器。我发现在一次性脚本(例如我在 Stack Overflow 上回答问题时编写的脚本)中保留通用 shebang 行很有用,这样我就可以在 Windows 和Arch Linux上快速测试它们。
env 实用程序允许您在路径上调用命令:
剩余的第一个参数指定要调用的程序名称;根据环境变量进行搜索
PATH
。任何剩余的参数都作为参数传递给该程序。
解决方案 3:
稍微扩展一下其他答案,这里有一个小例子,说明命令行脚本如何由于不谨慎使用 shebang 行而陷入麻烦/usr/bin/env
:
$ /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")
解决方案 4:
为了运行python脚本,我们需要告诉shell三件事:
该文件是一个脚本
我们希望使用哪个解释器来执行脚本
所述解释器的路径
Shebang#!
完成 (1.)。Shebang 以 开头,#
因为该#
字符在许多脚本语言中是注释标记。因此,解释器会自动忽略 Shebang 行的内容。
该env
命令实现了(2.)和(3.)。引用“gravity”的话,
该命令的一个常见用途
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 中。(除非不在 /usr/bin/env 中;某些系统可能会使用 /bin/env,但这种情况相当罕见,并且只会发生在非 Linux 系统上。)
解决方案 5:
exec
Linux 内核的系统调用能够原生地理解 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
例如 find /usr/bin/python
,然后最终调用:
/usr/bin/python /path/to/script.py
Python 解释器确实可以看到#!
文件中的该行,但#
它是 Python 中的注释字符,因此该行会被忽略,作为常规注释。
是的,你可以使用以下命令创建一个无限循环:
printf '#!/a
' | 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
)。让我们通过读取 的前 4 个字节来确认这一点/bin/ls
,这是一个 ELF 可执行文件:
head -c 4 "$(which ls)" | hd
输出:
00000000 7f 45 4c 46 |.ELF|
00000004
因此,当内核看到这些字节时,它会获取 ELF 文件,将其正确放入内存,并使用它启动一个新进程。另请参阅:内核如何获取在 Linux 下运行的可执行二进制文件?
最后,您可以使用该机制添加自己的 shebang 处理程序binfmt_misc
。例如,您可以为.jar
文件添加自定义处理程序。此机制甚至支持按文件扩展名处理程序。另一个应用是使用 QEMU 透明地运行不同体系结构的可执行文件。
不过,我认为POSIX并未指定 shebang:https://unix.stackexchange.com/a/346214/32558,尽管它确实在原理部分中提到过,并且形式为“如果系统支持可执行脚本,则可能会发生某些事情”。不过,macOS 和 FreeBSD 似乎也实现了它。
PATH
搜索动机
很可能,shebang 存在的一个主要动机是,在 Linux 中,我们经常希望运行以下命令PATH
:
basename-of-command
而不是:
/full/path/to/basename-of-command
但是,如果没有 shebang 机制,Linux 怎么知道如何启动每种类型的文件呢?
在命令中对扩展进行硬编码:
basename-of-command.py
或者在每个解释器上实现 PATH 搜索:
python basename-of-command
这是一种可能性,但是如果我们决定将命令重构为另一种语言,那么这将带来一个主要问题:一切都会崩溃。
Shebang 完美地解决了这个问题。
env
:pyenv
和其他版本管理器的主要用例
您应该使用#!/usr/bin/env python
而不是仅仅使用 的一个主要用例/usr/bin/python
是带有 的版本管理器pyenv
。
pyenv
允许您在一台机器上轻松安装多个 Python 版本,以便能够更好地重现其他项目而无需虚拟化。
然后,它通过在 PATH 中设置其顺序来管理“当前”python 版本:例如,如apt-get install 所示,对于不同的 python 版本, pyenv 管理的 python 可以位于:
/home/ciro/.pyenv/shims/python
/usr/bin/python
因此,与某些系统可能通过update-alternatives
符号链接处理的情形相差甚远。
解决方案 6:
也许你的问题是这样的:
如果你想使用:$python myscript.py
你根本不需要那一行。系统将调用 python,然后 python 解释器将运行你的脚本。
但如果你打算使用:$./myscript.py
像普通程序或 bash 脚本一样直接调用它,您需要编写该行来向系统指定使用哪个程序来运行它,(并且使其可执行chmod 755
)
解决方案 7:
这样做的主要原因是使脚本可以跨操作系统环境移植。
例如,在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
解决方案 8:
从技术上讲,在 Python 中,这只是一行注释。
仅当您从 shell(从命令行)运行 py 脚本时才使用此行。这被称为“ Shebang!”,它可用于各种情况,而不仅仅是 Python 脚本。
在这里,它指示 shell 启动特定版本的 Python(以处理文件的其余部分)。
解决方案 9:
强调大多数人忽略的一件事可能是有意义的,这可能会妨碍立即理解。当您python
在终端中输入时,通常不会提供完整路径。相反,可执行文件会在PATH
环境变量中查找。反过来,当您想直接执行 Python 程序时/path/to/app.py
,必须告诉 shell 要使用哪个解释器(通过hashbang,其他贡献者在上面解释了这一点)。
Hashbang 需要解释器的完整路径。因此,要直接运行 Python 程序,您必须提供 Python 二进制文件的完整路径,而该路径会有很大差异,尤其是考虑到使用virtualenv时。为了解决可移植性问题,使用了技巧/usr/bin/env
。后者最初旨在就地更改环境并在其中运行命令。当未提供任何更改时,它会在当前环境中运行命令,这实际上会导致相同的PATH
查找,从而达到相同的效果。
来源:unix stackexchange
解决方案 10:
这是一个 shell 约定,它告诉 shell 哪个程序可以执行该脚本。
#!/usr/bin/env python
解析为 Python 二进制文件的路径。
解决方案 11:
它只是指定您要使用的解释器。要理解这一点,请通过终端创建一个文件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
到 stdout,因为 python2 支持 print 运算符。所以,现在您已经学会了如何在脚本解释器之间切换。
解决方案 12:
这是文档中提出的推荐方法:
2.2.2. 可执行 Python 脚本
在 BSD 类 Unix 系统上,Python 脚本可以像 shell 脚本一样直接执行,只需将下面一行代码放入
#! /usr/bin/env python3.2
来自2. 使用 Python 解释器
解决方案 13:
你可以使用 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
解决方案 14:
在我看来,即使没有那一行,文件运行起来也是一样的。
如果是这样,那么也许您正在 Windows 上运行 Python 程序?Windows 不使用该行 — 相反,它使用文件扩展名来运行与文件扩展名关联的程序。
然而,在 2011 年,开发了一个“Python 启动器”,它(在某种程度上)模仿了 Windows 的 Linux 行为。这仅限于选择运行哪个 Python 解释器 — 例如,在安装了 Python 2 和 Python 3 的系统上选择两者。启动器可以选择通过py.exe
Python 安装来安装,并且可以与.py
文件关联,以便启动器检查该行并依次启动指定的 Python 解释器版本。
解决方案 15:
这更多的是历史信息而不是“真实”的答案。
记得在过去,你有很多类Unix 操作系统,其设计者都有自己对放置东西的位置的想法,有时根本不包括 Python、Perl、Bash 或许多其他 GNU/开源东西。
不同的 Linux 发行版也存在同样的问题。在 Linux(FHS 1之前的版本)上,你可能在/usr/bin/
或 中安装了 Python /usr/local/bin/
。或者可能尚未安装,因此你自行构建了 Python 并将其放入 中~/bin
。
/usr/
Solaris 是我工作过的最差的版本,部分原因是从 Berkeley Unix 过渡到 System V。你可能会在、/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),如果你/usr/bin/python
在 Red Hat Linux 工作站上进行硬编码,它将在 SGI 上出现问题……嗯,不,我认为 IRIX 将 Python 放在了正确的位置。但在 Sparc 工作站上它可能根本无法运行。
我怀念我的 Sparc 工作站。但不是很多。好吧,现在你让我在 eBay 上四处搜索。真糟糕。
1 文件系统层次标准。
2是的,有时人们仍然会做这样的事情。不,我没有在腰带上佩戴萝卜或洋葱。
解决方案 16:
如果你在虚拟环境中运行脚本,那么在工作时venv
执行将显示 Python 解释器的路径:which python
`venv`
~/Envs/venv/bin/python
请注意,虚拟环境的名称嵌入在 Python 解释器的路径中。因此,在脚本中硬编码此路径会导致两个问题:
如果你将脚本上传到存储库,则会强制其他用户使用相同的虚拟环境名称。前提是他们首先发现问题。
即使您在其他虚拟环境中拥有所有必需的包,您也无法在多个虚拟环境中运行该脚本。
因此,补充一下乔纳森的回答,理想的情况是#!/usr/bin/env python
,不仅要实现跨操作系统的可移植性,还要实现跨虚拟环境的可移植性!
解决方案 17:
该行#!/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
解决方案 18:
python2
考虑到和之间的可移植性问题python3
,您应该始终指定任一版本,除非您的程序与两个版本都兼容。
一些发行版现在已经将python
符号链接发送至- 不要依赖于。python3
`python`python2
PEP 394强调了这一点:
为了容忍跨平台的差异,所有需要调用 Python 解释器的新代码都不应指定 python,而应指定 python2 或 python3(或更具体的 python2.x 和 python3.x 版本;请参阅迁移说明)。在 shebang 中、从 shell 脚本调用时、通过 system() 调用时或在任何其他上下文中调用时都应进行此区分。
解决方案 19:
当您有多个版本的 Python 时,它会告诉解释器使用哪个版本的 Python 来运行程序。
解决方案 20:
它允许您选择想要使用的可执行文件;如果您安装了多个 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())
解决方案 21:
当你执行 python 文件时,你可以使用./file.py
file 是文件的名称。/usr/bin/env 是 PATH,那么 python 就是 python 2,python3 就是 python 3(duh)
#!/usr/bin/env python
也可以允许python文件被其他程序执行,只要使用chmod +x file.py
。
解决方案 22:
这告诉脚本 python 目录在哪里!
#! /usr/bin/env python
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)