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

问题:具有修改后环境的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)

我感觉到有更好的方法了。看起来还好吗?

I believe that running an external command with a slightly modified environment is a very common case. That’s how I tend to do it:

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

I’ve got a gut feeling that there’s a better way; does it look alright?


回答 0

我认为os.environ.copy()如果您不打算为当前进程修改os.environ会更好:

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

I think os.environ.copy() is better if you don’t intend to modify the os.environ for the current process:

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

回答 1

这取决于问题所在。如果要克隆和修改环境,一种解决方案可能是:

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

但这在某种程度上取决于被替换的变量是有效的python标识符,它们通常是有效的(您遇到频率不是字母数字+下划线的环境变量名称还是以数字开头的变量的频率是多少?)。

否则,您将可以编写如下内容:

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

在非常奇怪的情况下(您多久在环境变量名称中使用控制代码或非ASCII字符?),bytes您甚至(在python3上)都无法使用该结构的环境键。

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

That depends on what the issue is. If it’s to clone and modify the environment one solution could be:

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

But that somewhat depends on that the replaced variables are valid python identifiers, which they most often are (how often do you run into environment variable names that are not alphanumeric+underscore or variables that starts with a number?).

Otherwise you’ll could write something like:

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

In the very odd case (how often do you use control codes or non-ascii characters in environment variable names?) that the keys of the environment are bytes you can’t (on python3) even use that construct.

As you can see the techniques (especially the first) used here benefits on the keys of the environment normally is valid python identifiers, and also known in advance (at coding time), the second approach has issues. In cases where that isn’t the case you should probably look for another approach.


回答 2

您可以使用它,my_env.get("PATH", '')而不是my_env["PATH"]PATH某种方式在原始环境中未定义,但除此之外,它看起来还不错。

you might use my_env.get("PATH", '') instead of my_env["PATH"] in case PATH somehow not defined in the original environment, but other than that it looks fine.


回答 3

使用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),并且想要使用默认值覆盖字典,则可以这样表示:

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

With Python 3.5 you could do it this way:

import os
import subprocess

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

subprocess.Popen(my_command, env=my_env)

Here we end up with a copy of os.environ and overridden PATH value.

It was made possible by PEP 448 (Additional Unpacking Generalizations).

Another example. If you have a default environment (i.e. os.environ), and a dict you want to override defaults with, you can express it like this:

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

回答 4

要临时设置环境变量而不必复制os.envrion对象等,我可以这样做:

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

To temporarily set an environment variable without having to copy the os.envrion object etc, I do this:

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

回答 5

env参数接受字典。您可以简单地使用os.environ,在其中添加键(如果需要,可以将其添加到dict的副本中)(用作键)Popen

The env parameter accepts a dictionary. You can simply take os.environ, add a key (your desired variable) (to a copy of the dict if you must) to that and use it as a parameter to Popen.


回答 6

我知道已经回答了一段时间,但是有些人可能想知道一些有关在环境变量中使用PYTHONPATH而不是PATH的知识。我已经概述了使用cronjobs运行python脚本的说明,该说明以另一种方式(在此处找到)处理修改后的环境。认为这对像我这样需要的人仅比此答案所提供的要多。

I know this has been answered for some time, but there are some points that some may want to know about using PYTHONPATH instead of PATH in their environment variable. I have outlined an explanation of running python scripts with cronjobs that deals with the modified environment in a different way (found here). Thought it would be of some good for those who, like me, needed just a bit more than this answer provided.


回答 7

在某些情况下,您可能只希望传递子流程所需的环境变量,但是我认为您通常具有正确的想法(这也是我的做法)。

In certain circumstances you may want to only pass down the environment variables your subprocess needs, but I think you’ve got the right idea in general (that’s how I do it too).