Python子进程/Popen与修改后的环境

我相信在稍加修改的环境下运行外部命令是非常常见的情况。我就是这么做的:

import subprocess, os
my_env = os.environ
my_env["PATH"] = "/usr/sbin:/sbin:" + my_env["PATH"]
subprocess.Popen(my_command, env=my_env)

我有一种直觉,有更好的方法;看起来还好吗?

294978 次浏览

在某些情况下,您可能只想传递子进程所需的环境变量,但我认为您的想法大体是正确的(这也是我的做法)。

你可以使用my_env.get("PATH", '')而不是my_env["PATH"],以防PATH在原始环境中没有定义,但除此之外,它看起来很好。

参数env接受一个字典。你可以简单地取os。environ,将一个键(你想要的变量)(如果必须的话,到dict的副本)添加到它,并将其作为Popen的参数。

我认为os.environ.copy()更好,如果你不打算修改操作系统。当前过程的环境:

import subprocess, os
my_env = os.environ.copy()
my_env["PATH"] = "/usr/sbin:/sbin:" + my_env["PATH"]
subprocess.Popen(my_command, env=my_env)

我知道这个问题已经回答了一段时间了,但是对于在环境变量中使用PYTHONPATH而不是PATH,有些人可能想知道一些问题。我已经概述了使用cronjob运行python脚本的解释,它以不同的方式处理修改后的环境(在这里找到)。我认为这对那些像我一样需要比这个答案更多一点的人来说是有好处的。

那要看问题是什么。如果是克隆和修改环境,一个解决方案可以是:

subprocess.Popen(my_command, env=dict(os.environ, PATH="path"))

但这在某种程度上取决于被替换的变量是否是有效的python标识符,它们通常是有效的python标识符(您经常遇到环境变量名不是字母数字+下划线或变量以数字开头的情况吗?)

否则你可以这样写:

subprocess.Popen(my_command, env=dict(os.environ,
**{"Not valid python name":"value"}))

在非常奇怪的情况下(在环境变量名中使用控制码或非ascii字符的频率是多少?),环境的键是bytes,你甚至不能(在python3上)使用该构造。

正如您所看到的,这里使用的技术(特别是第一种)对环境键的好处通常是有效的python标识符,并且也事先知道(在编码时),第二种方法有问题。在不是这样的情况下,你可能应该寻找另一种方法

在Python 3.5中,你可以这样做:

import os
import subprocess


my_env = {**os.environ, 'PATH': '/usr/sbin:/sbin:' + os.environ['PATH']}


subprocess.Popen(my_command, env=my_env)

这里我们得到了os.environ的副本,并覆盖了PATH值。

它是由PEP 448(附加解包概括)实现的。

另一个例子。如果你有一个默认环境(即os.environ),并且你想用一个dict来覆盖默认值,你可以这样表示它:

my_env = {**os.environ, **dict_with_env_variables}

临时设置一个环境变量,而不必复制操作系统。envrion对象等,我这样做:

process = subprocess.Popen(['env', 'RSYNC_PASSWORD=foobar', 'rsync', \
'rsync://username@foobar.com::'], stdout=subprocess.PIPE)