我很困惑 subprocess
使用 Popen()
时搜索可执行文件.如果给定子进程的绝对路径,它就可以工作,但我正在尝试使用相对路径。我发现如果我设置了环境变量 PYTHONPATH 那么我可以从那个路径中获取导入的模块,并且 PYTHONPATH 就在 sys.path
中。 ,但它似乎对 subprocess.Popen
的行为没有帮助.我也试过编辑 sitecustomize.py
文件添加 PYTHONPATH 到 os.environ
,像这样
# copy PYTHONPATH environment variable into PATH to allow our stuff to use
# relative paths for subprocess spawning
import os
if os.getenv('PYTHONPATH') is not None and os.getenv('PATH') is not none:
os.environ['PATH'] = ':'.join([os.getenv('PATH'), os.getenv('PYTHONPATH')])
并验证在启动 python 时,无论是交互方式,使用 ipython,还是通过从命令行运行脚本,PYTHONPATH 成功出现在
os.environ
中。 .然而,subrocess.Popen
仍然没有在那里搜索可执行文件。我以为应该是继承了 parent 的环境,如果没有env
kwarg 是指定的?接下来我尝试给 env
明确地,首先通过复制 os.getenv
其次只是通过给予 env={'PATH': '/explicit/path/to/search/from'}
,它仍然没有找到可执行文件。现在我很难过。希望一个例子将有助于更清楚地解释我的问题:
/dir/subdir1/some_executable
/dir/subdir2/some_script.py
# some_script.py
from subprocess import Popen, PIPE
spam, eggs = Popen(['../subdir1/some_executable'], stdout=PIPE, stderr=PIPE).communicate()
如果我在
/dir/subdir2
我跑 python some_script.py
它有效,但如果我在 /dir
我跑 python subdir2/some_script.py
即使 /dir/subdir2
在 os.environ['PATH']
,然后子进程将抛出 OSError: [Errno 2] No such file or directory
.
最佳答案
(从评论中填写详细信息以单独回答)
首先,相对路径(包含斜杠的路径)永远不会在任何 PATH
中检查。 , 无论你做什么。它们仅相对于当前工作目录。如果需要解析相对路径,则必须搜索PATH
手动,或 munge PATH
包含子目录,然后只使用我在下面的建议中的命令名称。
如果要运行相对于 Python 脚本位置的程序,请使用 __file__
然后从那里找到程序的绝对路径,然后使用Popen
中的绝对路径.
在当前进程的环境变量中搜索PATH
其次,还有an issue in the Python bug tracker关于 Python 如何处理裸命令(无斜杠)。基本上,在 Unix/Mac Popen
表现得像 os.execvp
当参数env=None
(已经观察到并在最后记录了一些意外行为):
On POSIX, the class uses
os.execvp()
-like behavior to execute the child program.
这实际上适用于
shell=False
和 shell=True
,提供 env=None
.此行为的含义在函数 os.execvp
的文档中进行了解释。 :The variants which include a “p” near the end (
execlp()
,execlpe()
,execvp()
, andexecvpe()
) will use thePATH
environment variable to locate the program file. When the environment is being replaced (using one of theexec*e
variants, discussed in the next paragraph), the new environment is used as the source of thePATH
variable.
For
execle()
,execlpe()
,execve()
, andexecvpe()
(note that these all end in “e”), the env parameter must be a mapping which is used to define the environment variables for the new process (these are used instead of the current process’ environment); the functionsexecl()
,execlp()
,execv()
, andexecvp()
all cause the new process to inherit the environment of the current process.
引用的第二段暗示
execvp
将使用当前进程的环境变量。结合第一个引用的段落,我们推断出execvp
将使用环境变量 PATH
的值从当前进程的环境来看。这意味着 Popen
查看PATH
的值就像 Python 启动时一样(运行 Popen
实例化的 Python)并且没有任何更改 os.environ
会帮你解决这个问题。此外,在带有
shell=False
的 Windows 上, Popen
不理会PATH
,并且只会相对于当前工作目录进行查看。什么
shell=True
确实如果我们通过
shell=True
会发生什么至 Popen
?在这种情况下, Popen
simply calls the shell :The shell argument (which defaults to
False
) specifies whether to use the shell as the program to execute.
That is to say,
Popen
does the equivalent of:Popen(['/bin/sh', '-c', args[0], args[1], ...])
换句话说,用
shell=True
Python会直接执行/bin/sh
, 无需任何搜索(将参数 executable
传递给 Popen
可以改变这一点,看来如果是不带斜杠的字符串,那么 Python 会将其解释为 shell 程序的名称,以便在值中搜索PATH
来自当前进程的环境,即,当它在上述情况 shell=False
中搜索程序时)。反过来,
/bin/sh
(或我们的 shell executable
)将寻找我们想要在它自己的环境中运行的程序 PATH
,与 PATH
相同的 Python(当前进程),从上面的短语“也就是说...”之后的代码推导出来(因为该调用具有 shell=False
,所以这是前面已经讨论过的情况)。因此,execvp
类似行为是我们得到的 shell=True
和 shell=False
,只要env=None
.路过
env
至 Popen
那么如果我们通过 env=dict(PATH=...)
会发生什么?至 Popen
(因此在将由 PATH
运行的程序的环境中定义一个环境变量 Popen
)?在这种情况下,新环境用于搜索要执行的程序。引用
Popen
的文档:If env is not
None
, it must be a mapping that defines the environment variables for the new process; these are used instead of the default behavior of inheriting the current process’ environment.
结合上述观察,并从实验中使用
Popen
,这意味着 Popen
在这种情况下,其行为类似于函数 os.execvpe
.如 shell=False
, Python 在新定义的 PATH
中搜索给定程序.正如上面针对 shell=True
所讨论的那样,在这种情况下,程序是 /bin/sh
, 或者,如果程序名称与参数 executable
一起给出,然后在新定义的 PATH
中搜索这个替代(shell)程序.另外,如果
shell=True
,然后在 shell 中搜索路径,shell 将使用该路径查找 args
中给出的程序是 PATH
的值传递给 Popen
通过 env
.所以与
env != None
, Popen
搜索键值 PATH
的 env
(如果 key PATH
存在于 env
中)。传播除
PATH
以外的环境变量作为参数除了
PATH
之外,还有一个关于环境变量的警告。 :如果命令中需要这些变量的值(例如,作为正在运行的程序的命令行参数),那么即使这些存在于 env
中给 Popen
,如果没有 shell=True
,它们将不会被解释.这很容易避免而无需更改
shell=True
: 将这些值直接插入 list
参数args
给予 Popen
. (另外,如果这些值来自 Python 自身的环境,可以使用方法 os.environ.get
来获取它们的值)。使用
/usr/bin/env
如果你只需要路径评估并且不想通过 shell 运行你的命令行,并且在 UNIX 上,我建议使用 env
而不是 shell=True
,如path = '/dir1:/dir2'
subprocess.Popen(['/usr/bin/env', '-P', path, 'progtorun', other, args], ...)
这让您可以通过不同的 PATH
到 env
进程(使用选项 -P
),它将使用它来查找程序。它还避免了 shell 元字符问题和通过 shell 传递参数的潜在安全问题。显然,在 Windows(几乎是唯一没有 /usr/bin/env
的平台)上,您需要做一些不同的事情。关于
shell=True
引用 Popen
文档:If shell is
True
, it is recommended to passargs
as a string rather than as a sequence.
Note: Read the Security Considerations section before using
shell=True
.
意外观察
观察到以下行为:
FileNotFoundError
,正如预期的那样:subprocess.call(['sh'], shell=False, env=dict(PATH=''))
sh
,这是出乎意料的:subprocess.call(['sh'], shell=False, env=dict(FOO=''))
打字 echo $PATH
在此打开的 shell 内显示 PATH
值不为空,也不同于PATH
的值在 Python 环境中。所以似乎PATH
确实不是从 Python 继承的(正如预期的在 env != None
存在的情况下),但它仍然是 PATH
是非空的。不知道为什么会这样。FileNotFoundError
,正如预期的那样:subprocess.call(['tree'], shell=False, env=dict(FOO=''))
tree
,正如预期的那样:subprocess.call(['tree'], shell=False, env=None)
关于python子进程Popen环境路径?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5658622/