我需要更改Python应用程序调用的程序。不幸的是我无法更改Python代码。我只能更改调用环境(特别是PATH
(。但不幸的是,Python的子流程模块似乎忽略了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
调用,因为subprocess
文档指出
在POSIX上,类使用类似
os.execvp()
的行为来执行子程序。
不用说,从C调用的实际execvp
POSIX API确实尊重PATH
,因此这是一个特定于Python的问题。
1Ubuntu 18.04.2 LTS,Python 3.6.9。
execvp
的实现与POSIXexecvp
语义不一致。特别是Python不会通过将文件解释为shell脚本来响应ENOEXEC
错误,并且需要显式的shebang。
将文件创建为:
printf '#!/bin/shnecho foobarn' > ./whoami
使事情按预期进行
请注意,这已经知道一段时间了:https://bugs.python.org/issue19948
您的
echo 'echo foobar' >whoami
chmod +x whoami
运行不正确。
Python并没有获取可执行文件,即使设置了执行位,它也不知道需要先运行bash才能执行,因此它会跳过路径并运行原始的whoami,路径为/usr/bin/woami
添加舍邦
echo "#!/bin/sh" > whoami
echo 'echo foobar' >> whoami
chmod +x whoami
在Unix风格的系统(包括Linux/OSX(上,它所调用的shebang行即告诉加载程序(或内核,有时甚至是shell(使用哪个程序来运行文件。最基本的是,您需要指定一个到python解释器的路径。
我怀疑你/whoami(带有执行权限集(,shell正在做一些额外的魔术,所以你不必键入/bin/sh$PWD/whoami
如果你做
chmod-x whoami
你可以使用特殊的
/whoami(告诉shell将其作为shell脚本执行(。
请注意,execvp应该使用/bin/sh而不是bash。还有/whoami将取决于你碰巧使用的外壳,大多数都会";源";文件,而不是在另一个进程中执行它(即环境、工作目录等的更改将保留(
如果没有shebang或可执行文件头,shell只会将自己用作默认解释器(但只有当通过./whoami调用时;../whoami不同,它会获取文件源,而不管它是否可执行(。
execvp(POSIX,而不是Python(令人困惑的特性显然也起到了这个作用。Python在这种情况下失败了,因为os.execvp实际上并没有在后台调用execvp,它的相似之处只是名称上。