I need to change which program is called by a Python application. Unfortunately I cannot change the Python code. I can only change the calling environment (in particular, PATH). But unfortunately Python’s subprocess module seems to ignore PATH (at least under certain circumstances).
How can I force Python to respect PATH when searching which binary to invoke?
To illustrate the problem, here’s an MVCE. The actual Python application is using subprocess.check_output(['nvidia-smi', '-L']), but the following simplified code shows the same behaviour.
Create test.py:
import os
from subprocess import run
run(['which', 'whoami'])
run(['/usr/bin/env', 'whoami'])
run(['whoami'])
os.execvp('whoami', ['whoami'])
Now create a local whoami script and execute test.py:
echo 'echo foobar' >whoami
chmod +x whoami
PATH=.:$PATH python3 test.py
On my system1 this prints:
./whoami
foobar
konrad
konrad
I expect this code to always print foobar instead of konrad.
My MVCE includes the os.execvp call because the subprocess documentation states that
On POSIX, the class uses
os.execvp()-like behavior to execute the child program.
Needless to say, the actual execvp POSIX API, called from C, does respect PATH, so this is a Python specific issue.
1 Ubuntu 18.04.2 LTS, Python 3.6.9.
#!/bin/shat the beginning of yourwhoamiscript I get your expected behaviour, is omitting that deliberate? running it without givesENOEXEC (Exec format error)on the script#!/bin/shfixes it. Which makes sense - instraceI seeexecve("./whoami", ...) = -1 ENOEXEC (Exec format error)because of the missing shebang kernel can't execute the file - so/usr/bin/whoamiis chosen.execvewhile OP appears to be deliberately usingexecvp. the latter which should attempt to interpret using/bin/shif it getsENOEXECwhen executing directlyexcept (ENOEXEC): exec("sh", file...)is missingexecvp!). But that indeed fixes my issue, and if you write it up as an answer I can accept that! I’m still curious why it’s required here when it isn’t for the POSIX function (though thinking about it now I’m more surprised that the POSIX function doesn’t require it).