我相信在稍微修改过的环境下运行外部命令是很常见的情况。这就是我倾向于这样做的方式:
import subprocess, os
my_env = os.environ
my_env["PATH"] = "/usr/sbin:/sbin:" + my_env["PATH"]
subprocess.Popen(my_command, env=my_env)
我有一种直觉,认为有更好的方法;看起来还好吗?
我认为如果您不打算修改当前进程的 os.environ ,那么
os.environ.copy()
会更好:
import subprocess, os
my_env = os.environ.copy()
my_env["PATH"] = f"/usr/sbin:/sbin:{my_env['PATH']}"
subprocess.Popen(my_command, env=my_env)
这取决于问题所在。如果要克隆和修改环境,一种解决方案可能是:
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标识符,并且也可以提前知道(在编码时),第二种方法有问题。如果情况并非如此,您可能应该寻找另一种方法。
使用 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}
您可以使用
my_env.get("PATH", '')
而不是 my_env["PATH"]
,以防 PATH
在原始环境中未定义,但除此之外它看起来不错。
要临时设置环境变量而不必复制 os.envrion 对象等,我这样做:
process = subprocess.Popen(['env', 'RSYNC_PASSWORD=foobar', 'rsync', \
'rsync://[email protected]::'], stdout=subprocess.PIPE)
env 参数接受字典。您可以简单地使用 os.environ,添加一个键(您想要的变量)(如果必须的话,添加到字典的副本),并将其用作
Popen
的参数。
我知道这个问题已经得到解答一段时间了,但是有些人可能想了解有关在环境变量中使用 PYTHONPATH 而不是 PATH 的一些要点。我概述了使用 cronjobs 运行 python 脚本的解释,它以不同的方式处理修改后的环境(在此处找到)。我认为这对于那些像我一样需要比提供的答案更多一点的人来说会有一些好处。
在某些情况下,您可能只想传递子进程所需的环境变量,但我认为您总体上有正确的想法(我也是这样做的)。
在较新版本的 Python 中,您可以使用
env
函数的
run
关键字参数,如下所示:
import subprocess
import os
subprocess.run(["mycommand"], env=dict(os.environ) | {"FOO": "bar"})
因为我希望进程继承现有的环境变量,所以我使用 union 运算符将
dict(os.environ)
与我自己的覆盖环境变量字典合并,该运算符适用于 Python 3.9+。
这可能是一个解决方案:
new_env = dict([(k,(':'.join([env[k], v]) if k in env else v)) for k,v in os.environ.items()])