为什么子进程忽略 PATH,我该如何更改它?

Why is subprocess ignoring PATH, and how can I change this?

我需要更改 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

我的 MVCE 包含 os.execvp 调用,因为 the subprocess documentation 指出

On POSIX, the class uses os.execvp()-like behavior to execute the child program.

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


1 Ubuntu 18.04.2 LTS,Python 3.6.9.

根据我的评论,这是由于 Python 通过将文件解释为 shell 脚本来实现 execvp not being consistent with POSIX execvp semantics. in particular Python doesn't respond to ENOEXEC errors,并且需要明确的 shebang。

创建文件为:

printf '#!/bin/sh\necho foobar\n' > ./whoami

使事情按预期工作

请注意,这已经有一段时间了:https://bugs.python.org/issue19948

你的

echo 'echo foobar' >whoami
chmod +x whoami

运行 不正确。

Python 没有将其拾取为可执行文件,即使设置了执行位,它也不知道它需要先 运行 bash 才能执行,因此在路径上跳过 运行s 路径为 /usr/bin/whoami

的原始 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 你碰巧使用的是什么,大多数将“获取”文件而不是在另一个进程中执行它(即对环境、工作目录等的更改将保留)

如果没有 shebang 或可执行文件头,shell 简单地将自己用作默认解释器(但仅当通过 ./whoami 调用时;../whoami 不同,它获取文件,无论是否它是可执行的)。

execvp(POSIX,而不是 Python)的混乱性质显然也是如此。 Python 在这种情况下失败,因为 os.execvp 实际上并没有在后台调用 execvp,它的相似之处只是名义上的。