【问题标题】:python subprocess Popen environment PATH?python子进程Popen环境PATH?
【发布时间】:2011-08-05 05:49:40
【问题描述】:

我对使用Popen()subprocess 如何搜索可执行文件感到困惑。如果给定子进程的绝对路径,它可以工作,但我正在尝试使用相对路径。我发现如果我设置环境变量 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

【问题讨论】:

  • 在重读这个问题时,我想我看到了这个问题。在命令外壳中,切换到/dir,看看如果你输入../subdir1/some_executable会发生什么。
  • 好的,我明白你在说什么,我的误解是假设相对路径将被搜索为与裸程序调用相同。谢谢

标签: python path subprocess environment popen


【解决方案1】:

(填写评论中的详细信息以单独回答)

首先,无论您做什么,都不会在任何PATH 中检查相对路径(包含斜杠的路径)。它们仅相对于当前工作目录。如果您需要解析相对路径,则必须手动搜索 PATH,或者使用 PATH 包含子目录,然后按照我的建议使用命令名称。

如果你想运行一个程序相对于Python脚本的位置,使用__file__并从那里去寻找程序的绝对路径,然后在Popen.

在当前进程的环境变量PATH中搜索

其次,an issue in the Python bug tracker 关于 Python 如何处理裸命令(没有斜线)。基本上,在 Unix/Mac 上,Popen 的行为类似于 os.execvp,当参数 env=None 时(观察到一些意外行为并在末尾注明):

在 POSIX 上,该类使用类似os.execvp() 的行为来执行子程序。

shell=Falseshell=True 实际上都是如此,前提是 env=None。此行为的含义在函数os.execvp 的文档中进行了解释:

在结尾处包含“p”的变体(execlp()execlpe()execvp()execvpe())将使用PATH 环境变量来定位程序文件时间>。当环境被替换时(使用exec*e 变体之一,将在下一段中讨论),新环境将用作PATH 变量的源。

对于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 simply calls the shell:

shell 参数(默认为False)指定是否使用shell 作为程序来执行。

也就是说,Popen 相当于:

Popen(['/bin/sh', '-c', args[0], args[1], ...])

也就是说,有了shell=True,Python会直接执行/bin/sh,不需要任何搜索(将参数executable传递给Popen可以改变这一点,看来如果是不带斜线的字符串,那么它将被 Python 解释为 shell 程序的名称,以便在当前进程的环境中搜索 PATH 的值,即在上面描述的 shell=False 的情况下搜索程序。

反过来,/bin/sh(或者我们的shellexecutable)会在自己环境的PATH中寻找我们想要运行的程序,这和Python的PATH是一样的(当前进程) ,从上面的短语“也就是说...”之后的代码中推断出来(因为该调用具有shell=False,所以前面已经讨论过这种情况)。因此,execvp-like 行为是我们使用shell=Trueshell=False 得到的,只要env=None

env 传递给Popen

那么如果我们将env=dict(PATH=...) 传递给Popen(从而在Popen 运行的程序的环境中定义一个环境变量PATH)会发生什么?

在这种情况下,新环境用于搜索要执行的程序。引用Popen的文档:

如果env不是None,则必须是为新进程定义环境变量的映射;这些用于代替继承当前进程环境的默认行为。

结合上述观察结果,以及使用Popen 的实验,这意味着在这种情况下Popen 的行为类似于函数os.execvpe。如果shell=False,Python 在新定义的PATH 中搜索给定程序。正如上面已经讨论过的shell=True,在这种情况下程序是/bin/sh,或者,如果程序名称带有参数executable,则在新定义的@ 中搜索这个替代(shell)程序987654407@.

另外,如果shell=True,那么在shell内部shell用来查找args中给出的程序的搜索路径是传递给@987654411的PATH的值@通过env

因此,使用env != NonePopenenv 的键 PATH 的值中搜索(如果 env 中存在键 PATH)。

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

关于PATH以外的环境变量有一个警告:如果命令中需要这些变量的值(例如,作为正在运行的程序的命令行参数),那么即使这些变量存在于envPopen,如果没有 shell=True,它们将不会被解释。 无需更改shell=True 即可轻松避免这种情况:将这些值直接插入list 参数argsPopen。 (另外,如果这些值来自 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之前阅读Security Considerations部分。

意外观察

观察到以下行为:

  • 此调用引发FileNotFoundError,正如预期的那样:

    subprocess.call(['sh'], shell=False, env=dict(PATH=''))
    
  • 这个调用找到了sh,这是出乎意料的:

    subprocess.call(['sh'], shell=False, env=dict(FOO=''))
    

    在这个打开的shell里面输入echo $PATH会发现PATH的值不是空的,而且和Python环境下PATH的值也不同。所以看起来PATH 确实不是从Python 继承的(正如在env != None 的存在下所预期的那样),但是PATH 仍然是非空的。不知道为什么会这样。

  • 此调用引发FileNotFoundError,正如预期的那样:

    subprocess.call(['tree'], shell=False, env=dict(FOO=''))
    
  • 这找到了tree,正如预期的那样:

    subprocess.call(['tree'], shell=False, env=None)
    

【讨论】:

  • +1 "另外,在 shell=False 的 Windows 上,它根本不关注 PATH,只会相对于当前工作目录查找。"刚刚帮我解决了一个大问题 - 谢谢!
  • 一个在 Windows 上也可以工作的简单方法是明确地将 os.environ['PATH'] 作为参数 env 提供给 subprocess.Popen,如此处:stackoverflow.com/a/4453495/1959808 和那里:stackoverflow.com/a/20669704/1959808
  • /usr/bin/env 技巧不起作用,至少对于像 useradd 这样的系统命令和至少在 CentOS 中(cron 路径为空):/usr/bin/env: groupadd: No such file or directory
  • 如果 PATH 为空,这并不奇怪。 AFAIK,与 shell 不同, /usr/bin/env 没有它依赖的默认 PATH 。老实说,无论如何我都不建议依赖 shell 的默认 PATH。如果您正在编写 cron 作业,只需写出二进制文件的完整路径或自己设置 PATH。
  • 我有一个subprocess.Popen,它似乎用shell=False 搜索路径。然而,无效的是使用sys.path.append 增加路径以包含可执行文件的位置 - 我发现它仅在%PATH% 在 Python 程序启动之前包含可执行文件的路径时才有效。
【解决方案2】:

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

PATH 是一个环境变量,它告诉 OS shell 在哪里搜索可执行文件。

PYTHONPATH 是一个环境变量,它告诉 Python 解释器在哪里搜索要导入的模块。与subprocess查找可执行文件无关。

由于底层实现的差异,subprocess.Popen 默认只会在非 Windows 系统上搜索路径(Windows 有一些系统目录它总是搜索,但这与 PATH 处理不同)。扫描路径的唯一可靠的跨平台方法是将shell=True 传递给子进程调用,但这有其自身的问题(详见Popen documentation

但是,您的主要问题似乎是您将路径片段传递给Popen,而不是简单的文件名。一旦你有一个目录分隔符,你将禁用 PATH 搜索,即使在非 Windows 平台上也是如此(例如,请参阅exec family of functions 的 Linux 文档)。

【讨论】:

  • 这与 Python 文档不匹配。 The Popen docs 表明程序是通过 os.execvp 执行的——并且该调用确实考虑了 PATH 环境变量。此外,如果您只需要路径评估,我建议使用env 而不是shell=True,如Popen(['/usr/bin/env', 'progtorun', other, args], ...)。这避免了 shell 元字符的问题和通过 shell 传递参数的潜在安全问题。
  • 虽然它们都是 *NIX 特有的——它们不能在 Windows 上工作,所以我不喜欢推荐它们作为名义上跨平台模块的解决方法。你是对的,我的答案是不正确的,但会相应地编辑。
  • 已更新以明确默认情况下不搜索 PATH 是 Windows 独有的事情,但也指出了真正的问题(要执行的命令中的目录分隔符)。
  • 一个小改动。 subprocess.Popen 将在C:\Windows\System32 中获取可执行文件,如果您在 64 位 Windows 上运行 32 位 python,则它(我很高兴地弄清楚这一点)实际上是 C:\Windows\SysWOW64
  • @JohnOxley 我已经调整了答案以提及这一点,但是您是否知道任何好的参考链接?也许在某个地方的 MSDN 上?
【解决方案3】:

subprocess.Popen 中的相对路径相对于当前工作目录,而不是系统 PATH 的元素。如果您从/dir 运行python subdir2/some_script.py,那么预期的可执行位置(传递给Popen)将是/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()

【讨论】:

  • 呃,什么? subdir2/some_script.py 相对于 /dir 只是 /dir/subdir2/some_script.py
  • subdir2/some_script.py 处的 python 脚本执行Popen,可执行路径为../subdir1/some_executable。它是相对于当前工作主管/dir 解析的可执行路径,导致/dir/../subdir1/some_executable。请参阅沃尔特的回答,它以不同的方式说同样的事情。我本可以更好地表达我的答案。干杯!编辑:看起来我的答案也有错字,在我的意思是subdir1的可执行路径中使用subdir2
【解决方案4】:

pythonpath 设置为执行 python 解释器的路径。因此,在您的示例的第二种情况下,路径设置为 /dir 而不是 /dir/subdir2 这就是你得到错误的原因。

【讨论】:

  • 我不相信这是正确的,因为如果我编写一个简单的脚本来打印 os.environ ,那么无论我从哪里运行解释器,PYTHONPATH 都是一样的。 PYTHONPATH 在 /etc/environment 中设置,用于增加模块的搜索路径
  • 我的意思是说执行python的目录,那个目录被添加到pythonpath中。在第二种情况下,添加了/dir,而不是/dir/subdir2。因此,您可以更改代码以反映更改(一种方法是将 /dir/subdir2 添加到代码中的 os.path )或从适当的目录启动 python。
猜你喜欢
  • 2011-01-14
  • 2018-12-05
  • 2014-09-15
  • 1970-01-01
  • 2015-03-18
  • 2012-03-03
  • 2017-02-03
  • 2017-08-22
相关资源
最近更新 更多