python子进程Popen环境路径?

标签 python path subprocess environment 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仍然没有在那里搜索可执行文件。我以为应该是继承了 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/subdir2os.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=Falseshell=True ,提供 env=None .此行为的含义在函数 os.execvp 的文档中进行了解释。 :

The variants which include a “p” near the end (execlp(), execlpe(), execvp(), and execvpe()) will use the PATH environment variable to locate the program file. When the environment is being replaced (using one of the exec*e variants, discussed in the next paragraph), the new environment is used as the source of the PATH variable.


For execle(), execlpe(), execve(), and execvpe() (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 functions execl(), execlp(), execv(), and execvp() 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=Trueshell=False ,只要env=None .
路过envPopen那么如果我们通过 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搜索键值 PATHenv (如果 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 pass args 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/

    相关文章:

    python - 使用 matplotlib 显示六边形网格

    python - API 可以告诉 Pylint 不要在客户端代码中提示吗?

    c++ - 我应该在哪里保存代码块项目?

    python - python 中的子进程错误

    python-3.x - 将 split() 与 subprocess.check_output() 结合使用

    python - 每次结果都不一样

    python - Python 中的相同对象问题

    PowerShell - 将祖父文件夹名称添加到文件名

    docker - 如何在子文件夹中使用 go.mod 构建 golang docker 镜像?

    python - 如何从python将命令传递给应用程序