为什么subprocess忽略了PATH,我该如何改变这种情况?

3
我需要更改由Python应用程序调用的程序。不幸的是,我无法更改Python代码。我只能更改调用环境(特别是PATH)。但不幸的是,在某些情况下,Python的subprocess模块似乎忽略了PATH如何强制Python在搜索要调用的二进制文件时尊重PATH 为了说明问题,这里有一个MVCE(最小可复现例子)。实际的Python应用程序使用subprocess.check_output(['nvidia-smi', '-L']),但以下简化的代码展示了相同的行为。
创建test.py
import os
from subprocess import run

run(['which', 'whoami'])
run(['/usr/bin/env', 'whoami'])
run(['whoami'])

os.execvp('whoami', ['whoami'])

现在创建一个本地的whoami脚本并执行test.py:
echo 'echo foobar' >whoami
chmod +x whoami
PATH=.:$PATH python3 test.py

在我的系统1上,这会打印出如下内容:

./whoami
foobar
konrad
konrad

我希望这段代码总是输出foobar而不是konrad

我的最小可复现示例包括os.execvp的调用,因为 subprocess 文档指出:

在 POSIX 上,该类使用 os.execvp() 的行为来执行子进程程序。

不用说,实际的execvp POSIX API 从 C 调用时会尊重PATH,所以这是一个特定于 Python 的问题。


1 Ubuntu 18.04.2 LTS,Python 3.6.9。


2
如果我在你的 whoami 脚本开头放置 #!/bin/sh,那么我会得到你期望的行为,省略它是有意为之吗?没有加上这个会导致脚本运行时出现 ENOEXEC (Exec format error) - Sam Mason
1
我可以重现这个问题,并且添加 #!/bin/sh 可以解决它。这是有道理的 - 在 strace 中,我看到 execve("./whoami", ...) = -1 ENOEXEC (Exec format error),因为缺少 shebang,内核无法执行该文件 - 所以选择了 /usr/bin/whoami - KamilCuk
1
@KamilCuk 注意,strace使用execve,而OP似乎故意使用execvp。后者应该在直接执行时尝试使用/bin/sh进行解释,如果出现ENOEXEC错误。 - Sam Mason
[os.py#L608 _execvpe()](https://github.com/python/cpython/blob/master/Lib/os.py#L608)我猜缺少一个`except(ENOEXEC):exec(“sh”,file ...)` - KamilCuk
@SamMason,这并不是故意的,但我认为它不应该是必要的(对于POSIX的execvp也不是!)。但这确实解决了我的问题,如果你把它写成答案,我可以接受!我仍然很好奇为什么在这里需要它,而对于POSIX函数却不需要它(尽管现在我想想,我更惊讶的是POSIX函数不需要它)。 - Konrad Rudolph
https://dev59.com/z18d5IYBdhLWcg3wcx5V 很好地涵盖了这个问题。似乎Python无法确定它是什么类型的可执行文件,或者它是二进制文件还是shell脚本等等。设置chmod + x只是故事的一半。 - The Unix Janitor
2个回答

2
根据我的评论,这是因为Python对execvp的实现与POSIXexecvp语义不一致。具体来说,Python无法响应ENOEXEC错误,并且需要一个显式的shebang来解释文件作为shell脚本。
创建文件如下:
printf '#!/bin/sh\necho foobar\n' > ./whoami

使事情按预期工作的原因

请注意,这已经被人们知晓一段时间:https://bugs.python.org/issue19948


1

你的

echo 'echo foobar' >whoami
chmod +x whoami

无法正常运行。

Python没有识别出它是可执行文件,即使设置了执行权限,它也不知道需要先运行bash才能执行,因此会跳过它在路径上的位置并运行原始的whoami,该whoami位于路径/usr/bin/whoami。

添加shebang。

echo "#!/bin/sh" > whoami
echo 'echo foobar' >> whoami
chmod +x whoami

在类Unix系统上(包括Linux/OS X),所谓的shebang行告诉加载器(或内核,或偶尔是shell)要使用哪个程序来运行文件。最基本的情况下,你需要指定Python解释器的路径。
我怀疑如果你执行./whoami(设置了执行权限),shell会做一些额外的魔法,这样你就不必输入/bin/sh $PWD/whoami。
如果你执行
chmod -x whoami
你可以使用特殊的
. ./whoami(告诉shell将其作为shell脚本执行)。
请注意,execvp应该使用/bin/sh而不是bash。另外,. ./whoami将取决于您使用的shell,大多数shell将“源”文件而不是在另一个进程中执行它(即环境、工作目录等的更改将保留)。
如果没有shebang或可执行头,则shell仅将自身用作默认解释器(但仅当通过./whoami调用时;. ./whoami不同,它会源化文件,无论它是否可执行)。
POSIX中的execvp函数(不是Python中的)本质上也具有混乱的特性。在这种情况下,Python失败了,因为os.execvp实际上并没有在底层调用execvp函数,它们之间只是名称相似。

1
请注意,execvp 应该使用 /bin/sh 而不是 bash。此外,. ./whoami 将取决于您使用的 shell,大多数 shell 将“源”文件而不是在另一个进程中执行它(即环境、工作目录等的更改将保留)。 - Sam Mason
是的,如果没有给出shebang或可执行头文件,shell会简单地将自己用作默认解释器(但仅在通过./whoami调用时如此;. ./whoami不同,它文件,无论它是否可执行)。让我困惑的是,execvp(POSIX,而不是Python)显然也这样做。而Python失败是因为os.execvp实际上不会调用execvp - Konrad Rudolph
很好的评论,我会把它加入我的答案..谢谢@SamMason和Konrad RuDolph - The Unix Janitor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接