[英]python subprocess Popen environment PATH?
我对使用Popen()
时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
仍然不会在那里搜索可执行文件。 如果没有指定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
,或者修改PATH
以包含子目录,然后只需使用我下面的建议中的命令名称。
如果要运行相对于 Python 脚本位置的程序,请使用__file__
并从那里找到程序的绝对路径,然后使用Popen
的绝对路径。
PATH
其次, Python 错误跟踪器中存在关于 Python 如何处理裸命令(无斜杠)的问题。 基本上,在Unix / Mac的Popen
行为就像os.execvp
当参数env=None
(一些意想不到的行为已经观察到,并指出在最后):
在 POSIX 上,该类使用
os.execvp()
类的行为来执行子程序。
这对于shell=False
和shell=True
实际上都是shell=True
,前提是env=None
。 函数os.execvp
的文档中解释了此行为的含义:
在末尾包含“p”的变体(
execlp()
、execlpe()
、execvp()
和execvpe()
)将使用PATH
环境变量来定位程序文件。 当环境被替换时(使用exec*e
变体之一,在下一段中讨论),新环境被用作PATH
变量的源。
对于
execlpe()
execle()
、execlpe()
、execve()
和execvpe()
(注意这些都以“e”结尾), env参数必须是一个映射,用于定义新进程的环境变量(这些用于代替当前进程的环境); 函数execl()
、execlp()
、execv()
和execvp()
都会导致新进程继承当前进程的环境。
引用的第二段暗示execvp
将使用当前进程的环境变量。 结合第一个引用的段落,我们推断execvp
会从当前进程的环境中使用环境变量PATH
的值。 这意味着Popen
查看PATH
的值,因为它在 Python 启动时(运行Popen
实例化的 Python)并且没有任何更改os.environ
可以帮助您解决这个问题。
此外,在带有shell=False
Windows 上, Popen
根本不关注PATH
,只会查看相对于当前工作目录的内容。
shell=True
做什么如果我们将shell=True
传递给Popen
会发生什么? 在这种情况下, Popen
只需调用 shell :
shell参数(默认为
False
)指定是否使用 shell 作为要执行的程序。
也就是说,
Popen
作用相当于:Popen(['/bin/sh', '-c', args[0], args[1], ...])
换句话说,with shell=True
Python 会直接执行/bin/sh
,无需任何搜索(将参数executable
传递给Popen
可以改变这一点,而且看起来如果它是一个没有斜线的字符串,那么它会被 Python 解释作为 shell 程序的名称,在当前进程的环境中在PATH
的值中搜索,即,当它在上述shell=False
的情况下搜索程序时)。
反过来, /bin/sh
(或我们的 shell executable
)将在其自身环境的PATH
查找我们想要运行的程序,该程序与 Python(当前进程)的PATH
相同,从后面的代码推断上面的短语“也就是说...”(因为该调用具有shell=False
,因此前面已经讨论过这种情况)。 因此,只要env=None
,我们就可以通过shell=True
和shell=False
类似execvp
的行为。
env
传递给Popen
那么如果我们将env=dict(PATH=...)
传递给Popen
(从而在由Popen
运行的程序的环境中定义一个环境变量PATH
)会发生什么?
在这种情况下,新环境用于搜索要执行的程序。 引用的文件Popen
:
如果env不是
None
,则它必须是定义新进程的环境变量的映射; 这些用于代替继承当前进程环境的默认行为。
与上述观察相结合,并从实验使用Popen
,这意味着Popen
在这种情况下表现得如同功能os.execvpe
。 如果shell=False
,Python 在新定义的PATH
搜索给定的程序。 正如上面针对shell=True
已经讨论的那样,在这种情况下,程序是/bin/sh
,或者,如果程序名称与参数executable
一起给出,则在新定义的PATH
搜索此替代(shell)程序。
此外,如果shell=True
,则在 shell 内,shell用于查找args
给出的程序的搜索路径是PATH
通过env
传递给Popen
的值。
因此,与env != None
, Popen
搜索在键值PATH
的env
(如果关键PATH
出现在env
)。
PATH
以外的环境变量作为参数传播关于PATH
以外的环境变量有一个警告:如果命令中需要这些变量的值(例如,作为正在运行的程序的命令行参数),那么即使这些变量存在于提供给Popen
的env
,如果没有shell=True
它们将不会被解释。 这很容易避免,而无需更改shell=True
:将这些值直接插入提供给Popen
的list
参数args
中。 (此外,如果这些值来自 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
文档:
如果shell是
True
,建议将args
作为字符串而不是序列传递。
注意:在使用
shell=True
之前阅读安全注意事项部分。
观察到以下行为:
正如预期的那样,此调用引发FileNotFoundError
:
subprocess.call(['sh'], shell=False, env=dict(PATH=''))
这个调用找到了sh
,这是出乎意料的:
subprocess.call(['sh'], shell=False, env=dict(FOO=''))
打字echo $PATH
这将打开外壳里面揭示了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)
您似乎对PATH
和PYTHONPATH
的性质有些困惑。
PATH
是一个环境变量,它告诉 OS shell 在哪里搜索可执行文件。
PYTHONPATH
是一个环境变量,它告诉 Python 解释器在哪里搜索要导入的模块。 它与subprocess
进程查找可执行文件无关。
由于底层实现的差异, subprocess.Popen
只会在非 Windows 系统上默认搜索路径(Windows 有一些它总是搜索的系统目录,但这与PATH
处理不同)。 扫描路径的唯一可靠的跨平台方法是将shell=True
传递给子进程调用,但这有其自身的问题(如Popen
文档中所述)
但是,您的主要问题似乎是您将路径片段传递给Popen
而不是简单的文件名。 只要您在那里有了目录分隔符,您就会禁用PATH
搜索,即使在非 Windows 平台上也是如此(例如,请参阅exec 函数系列的 Linux 文档)。
subprocess.Popen 中的相对路径相对于当前工作目录起作用,而不是系统路径的元素。 如果您从/dir
运行python subdir2/some_script.py
那么预期的可执行位置(传递给/dir/../subdir1/some_executable
)将是/dir/../subdir1/some_executable
,又名/subdir1/some_executable
而不是/dir/subdir1/some_executable
。
如果您肯定想使用从脚本自己的目录到特定可执行文件的相对路径,最好的选择是首先从__file__
全局变量的目录部分构造一个绝对路径。
#/usr/bin/env python
from subprocess import Popen, PIPE
from os.path import abspath, dirname, join
path = abspath(join(dirname(__file__), '../subdir1/some_executable'))
spam, eggs = Popen(path, stdout=PIPE, stderr=PIPE).communicate()
pythonpath 设置为执行 python 解释器的路径。 因此,在您的示例的第二种情况下,路径设置为 /dir 而不是 /dir/subdir2 这就是您收到错误的原因。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.