繁体   English   中英

python子进程Popen环境路径?

[英]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/subdir2os.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=Falseshell=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=Trueshell=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 != NonePopen搜索在键值PATHenv (如果关键PATH出现在env )。

PATH以外的环境变量作为参数传播

关于PATH以外的环境变量有一个警告:如果命令中需要这些变量的值(例如,作为正在运行的程序的命令行参数),那么即使这些变量存在于提供给Popenenv ,如果没有shell=True它们将不会被解释。 这很容易避免,而无需更改shell=True :将这些值直接插入提供给Popenlist参数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文档:

如果shellTrue ,建议将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)

您似乎对PATHPYTHONPATH的性质有些困惑。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM