我应该使用什么 shebang 来始终指向 python3?
What shebang should I use to consistently point to python3?
我有一个使用 shebang #!/usr/bin/env python
的脚本。它在 Python 3 是唯一可用版本的机器上运行良好,但在同时具有 Python 2 和 Python 3 的机器上,它使用 Python 2 运行脚本.
如果我将 shebang 修改为 #!/usr/bin/env python3
,它将在 Python 2 和 Python 3 的机器上运行,但在只有 [=22= 的机器上] 3,它会失败并出现“没有这样的文件或目录”错误。
一个解决方案是创建一个别名 alias python=python3
。
是否有其他解决方案可以让相同的 shebang 在每台机器上统一工作?
不幸的是,没有一种通用的方法可以在任何和所有预先未知的 Linux 主机上工作,并且您在很大程度上受发行版维护者和本地主机配置的支配。
alias
无济于事,因为由内核处理的 #!
指定的解释器和 /usr/bin/env
在这种情况下它将执行并不知道你的 [=62] 的别名=].
当使用 env
时,您可以确保第一个找到 env
之后的名称,并且表示您希望它表示的含义:
- 确保所有主机在这方面都以相同的方式设置(安装预期的软件包或至少创建符号链接)
- 具有用于执行脚本的用户特定构造,例如:
mkdir /tmp/bin
ln -s /usr/bin/python /tmp/bin/python3
PATH="/tmp/bin:${PATH}" ./myscript.py
但是 none 这真的很棒,最终满足了您的要求。
你的解释器(这比听起来更难,解释器解析代码非常简单;把它放在哪里,如何调用它以便内核找到和使用它)也可以是一个简单的 shell 脚本试图弄清楚你用 python 代码打包,但恐怕你看到的任何选项都不是很好。
在 U*X-like 系统上建议/预期有一个 PEP-394:
- 你得到
python
python2
- 和
python3
对于 python3
但它认识到这一点从未得到完全一致的应用……而且在 2020 年也没有那么有用:
However, these recommendations implicitly assumed that Python 2 would always be available. As Python 2 is nearing its end of life in 2020 (PEP 373, PEP 404), distributions are making Python 2 optional or removing it entirely. This means either removing the python command or switching it to invoke Python 3. Some distributors also decided that their users were better served by ignoring the PEP's original recommendations, and provided system administrators with the freedom to configure their systems based on the needs of their particular environment.
TL;DR 不幸的是,没有办法普遍适用并补偿各种发行版甚至个别主机维护者的决定。 :(
我很可能会选择坚持使用 #!/usr/bin/env python3
(这是迄今为止推荐的命名方式)并添加一个 README
来解释先决条件以及如何设置主机,以确保万无一失.
为了完整起见,我应该补充一点,PEP 确实在这方面提出了建议:设置和使用虚拟环境或使用(第三方)环境管理器。但是,我阅读问题的方式是:“不对目标主机配置做出任何假设,也不对目标主机配置提出任何(附加)要求的便携式解释器规范”,这将不符合要求,也不意味着比说有实质性的改进:确保在搜索路径中有 python3
可执行文件,如果没有则创建一个符号链接。
#!/usr/bin/env python3
是最正确的通用解决方案。 在安装了 Python 3 的系统上,这应该有效,不管是否安装了Python2.
也就是说,仍然不能保证 正确 版本的 Python 3 安装,并且 hard-coding,比方说,python3.7
不可行,因为大多数用户不会安装多个版本。
幸运的是,有更好的解决方案:如果您将应用程序作为 Python 包 和 Setuptools 分发,并为您的 [指定 entry_points
=78=],安装过程将选择正确的解释器和 hard-code 它的路径进入您的应用程序脚本。无论环境如何,只要安装程序(例如 pip
)可以找到 Python。
作为一个非常简单的示例,假设您的项目 example
具有以下文件夹结构:
example
├── example
│ ╰── __init__.py
├── pyproject.toml
╰── setup.cfg
假设您的__init__.py
有一个函数main
,我们想将其用作可执行脚本的入口点。
那么 pyproject.toml
看起来像这样:1
[build-system]
requires = ["setuptools", "wheel"]
和setup.cfg
看起来如下:1,2
[metadata]
name = example
version = 0.0.1
[options]
packages = find:
[options.entry_points]
console_scripts =
example-bin = example:main
现在您可以使用例如pip3 bdist path-to-project
,这将生成 Wheel 安装包。当您安装它时(将其上传到 PyPI 或在本地(通过 pip3 install wheel-filename.whl
),pip3 将安装二进制文件 example-bin
.
example-bin
将启动您的包的入口点功能,并且无论 Python 3 二进制文件在您的系统上调用什么,它都会工作,因为 pip3 install …
使用绝对shebang 行中的路径。例如,在我的系统上,该文件的第一行是
#!/usr/local/opt/python/bin/python3.7
1 而不是使用 pyproject.toml
和 setup.cfg
,使用遗留的 setup.py
文件同样有效——但使用上面的更简单.
2 请注意,在撰写本文时,Setuptools 快速入门中有一个拼写错误:而不是 [entry_point]
,它应该是 [ entry_point<strong>s</strong>]
.
这是我目前确定的,尽管我仍在尝试编写我的代码,以便在必要的情况下它可以与 Python 2 一起工作。如果你有一些已知的路径,你可以根据你的需要修改它,例如(它不需要都在一行上):
#!/bin/bash
_='''' # Prefer python3 if it exists
command -v python3 &> /dev/null && exec python3 [=10=] "$@" || exec python [=10=] "$@"
'''
# Python code here
print ("Hello")
这是基于此页面中关于混合脚本的示例:https://gist.github.com/andyneff/fafba17b748bba6d7cd5
脚本最初加载在bash,它看到第一行以_=''''
开头,被bash解释为设置变量_
为二连续空字符串 (''
)。
第二行使用 command -v python3
来尝试确定 python3
是否在路径中或定义为别名,并将 stdout 和 stderr 重定向到空设备。如果它有一个成功的 return 代码,那么 exec python3 [=16=] "$@"
就是 运行,其中 [=17=]
是脚本的名称,"$@"
扩展到参数列表。如果失败,等效的 python
命令是 运行.
由于命令是 exec
,python3/python 命令替换了当前的 bash 进程,并且 bash 永远不会看到以下行。
当 Python 读取文件时,它会看到 _=''''
并将其解释为 '''...'''
多行三引号字符串的开头,即使它以四个撇号开头而不是只有三个,所以脚本以一个无害的额外 _
变量集结束。
我有一个使用 shebang #!/usr/bin/env python
的脚本。它在 Python 3 是唯一可用版本的机器上运行良好,但在同时具有 Python 2 和 Python 3 的机器上,它使用 Python 2 运行脚本.
如果我将 shebang 修改为 #!/usr/bin/env python3
,它将在 Python 2 和 Python 3 的机器上运行,但在只有 [=22= 的机器上] 3,它会失败并出现“没有这样的文件或目录”错误。
一个解决方案是创建一个别名 alias python=python3
。
是否有其他解决方案可以让相同的 shebang 在每台机器上统一工作?
不幸的是,没有一种通用的方法可以在任何和所有预先未知的 Linux 主机上工作,并且您在很大程度上受发行版维护者和本地主机配置的支配。
alias
无济于事,因为由内核处理的 #!
指定的解释器和 /usr/bin/env
在这种情况下它将执行并不知道你的 [=62] 的别名=].
当使用 env
时,您可以确保第一个找到 env
之后的名称,并且表示您希望它表示的含义:
- 确保所有主机在这方面都以相同的方式设置(安装预期的软件包或至少创建符号链接)
- 具有用于执行脚本的用户特定构造,例如:
mkdir /tmp/bin ln -s /usr/bin/python /tmp/bin/python3 PATH="/tmp/bin:${PATH}" ./myscript.py
但是 none 这真的很棒,最终满足了您的要求。
你的解释器(这比听起来更难,解释器解析代码非常简单;把它放在哪里,如何调用它以便内核找到和使用它)也可以是一个简单的 shell 脚本试图弄清楚你用 python 代码打包,但恐怕你看到的任何选项都不是很好。
在 U*X-like 系统上建议/预期有一个 PEP-394:
- 你得到
python
python2 - 和
python3
对于 python3
但它认识到这一点从未得到完全一致的应用……而且在 2020 年也没有那么有用:
However, these recommendations implicitly assumed that Python 2 would always be available. As Python 2 is nearing its end of life in 2020 (PEP 373, PEP 404), distributions are making Python 2 optional or removing it entirely. This means either removing the python command or switching it to invoke Python 3. Some distributors also decided that their users were better served by ignoring the PEP's original recommendations, and provided system administrators with the freedom to configure their systems based on the needs of their particular environment.
TL;DR 不幸的是,没有办法普遍适用并补偿各种发行版甚至个别主机维护者的决定。 :(
我很可能会选择坚持使用 #!/usr/bin/env python3
(这是迄今为止推荐的命名方式)并添加一个 README
来解释先决条件以及如何设置主机,以确保万无一失.
为了完整起见,我应该补充一点,PEP 确实在这方面提出了建议:设置和使用虚拟环境或使用(第三方)环境管理器。但是,我阅读问题的方式是:“不对目标主机配置做出任何假设,也不对目标主机配置提出任何(附加)要求的便携式解释器规范”,这将不符合要求,也不意味着比说有实质性的改进:确保在搜索路径中有 python3
可执行文件,如果没有则创建一个符号链接。
#!/usr/bin/env python3
是最正确的通用解决方案。 在安装了 Python 3 的系统上,这应该有效,不管是否安装了Python2.
也就是说,仍然不能保证 正确 版本的 Python 3 安装,并且 hard-coding,比方说,python3.7
不可行,因为大多数用户不会安装多个版本。
幸运的是,有更好的解决方案:如果您将应用程序作为 Python 包 和 Setuptools 分发,并为您的 [指定 entry_points
=78=],安装过程将选择正确的解释器和 hard-code 它的路径进入您的应用程序脚本。无论环境如何,只要安装程序(例如 pip
)可以找到 Python。
作为一个非常简单的示例,假设您的项目 example
具有以下文件夹结构:
example
├── example
│ ╰── __init__.py
├── pyproject.toml
╰── setup.cfg
假设您的__init__.py
有一个函数main
,我们想将其用作可执行脚本的入口点。
那么 pyproject.toml
看起来像这样:1
[build-system]
requires = ["setuptools", "wheel"]
和setup.cfg
看起来如下:1,2
[metadata]
name = example
version = 0.0.1
[options]
packages = find:
[options.entry_points]
console_scripts =
example-bin = example:main
现在您可以使用例如pip3 bdist path-to-project
,这将生成 Wheel 安装包。当您安装它时(将其上传到 PyPI 或在本地(通过 pip3 install wheel-filename.whl
),pip3 将安装二进制文件 example-bin
.
example-bin
将启动您的包的入口点功能,并且无论 Python 3 二进制文件在您的系统上调用什么,它都会工作,因为 pip3 install …
使用绝对shebang 行中的路径。例如,在我的系统上,该文件的第一行是
#!/usr/local/opt/python/bin/python3.7
1 而不是使用 pyproject.toml
和 setup.cfg
,使用遗留的 setup.py
文件同样有效——但使用上面的更简单.
2 请注意,在撰写本文时,Setuptools 快速入门中有一个拼写错误:而不是 [entry_point]
,它应该是 [ entry_point<strong>s</strong>]
.
这是我目前确定的,尽管我仍在尝试编写我的代码,以便在必要的情况下它可以与 Python 2 一起工作。如果你有一些已知的路径,你可以根据你的需要修改它,例如(它不需要都在一行上):
#!/bin/bash
_='''' # Prefer python3 if it exists
command -v python3 &> /dev/null && exec python3 [=10=] "$@" || exec python [=10=] "$@"
'''
# Python code here
print ("Hello")
这是基于此页面中关于混合脚本的示例:https://gist.github.com/andyneff/fafba17b748bba6d7cd5
脚本最初加载在bash,它看到第一行以_=''''
开头,被bash解释为设置变量_
为二连续空字符串 (''
)。
第二行使用 command -v python3
来尝试确定 python3
是否在路径中或定义为别名,并将 stdout 和 stderr 重定向到空设备。如果它有一个成功的 return 代码,那么 exec python3 [=16=] "$@"
就是 运行,其中 [=17=]
是脚本的名称,"$@"
扩展到参数列表。如果失败,等效的 python
命令是 运行.
由于命令是 exec
,python3/python 命令替换了当前的 bash 进程,并且 bash 永远不会看到以下行。
当 Python 读取文件时,它会看到 _=''''
并将其解释为 '''...'''
多行三引号字符串的开头,即使它以四个撇号开头而不是只有三个,所以脚本以一个无害的额外 _
变量集结束。