【问题标题】:Python subprocess/Popen with a modified environment修改环境的 Python 子进程/Popen
【发布时间】:2011-01-14 22:21:06
【问题描述】:

我相信在稍加修改的环境下运行外部命令是很常见的情况。这就是我倾向于这样做的方式:

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

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

【问题讨论】:

  • 对于跨平台工作的路径,也更喜欢使用os.pathsep 而不是“:”。见stackoverflow.com/questions/1499019/…
  • @phaedrus 我不确定当他使用 /usr/sbin 之类的路径时这是否非常相关 :-)

标签: python subprocess popen


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 如果您只想添加一个新的环境变量,这是最简单的答案。 os.environ['SOMEVAR'] = 'SOMEVAL'
      【解决方案4】:

      如果你不打算为当前进程修改 os.environ,我认为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)
      

      【讨论】:

      • >>> env = os.environ.copy >>> env['foo'] = 'bar' Traceback(最近一次调用最后):文件“”,第 1 行,在 TypeError: 'instancemethod' 对象不支持项目分配
      • @user1338062 您正在将实际方法os.environ.copy 分配给env 变量,但您需要将调用方法os.environ.copy() 的结果分配给env
      • 只有在 subprocess.Popen 调用中使用 shell=True 时,环境变量解析才真正起作用。请注意,这样做有潜在的安全隐患。
      • inside subprocess.Popen(my_command, env=my_env) -- 什么是“my_command”
      • @avinash - my_command 只是运行命令。例如,它可以是 /path/to/your/own/program 或任何其他“可执行”语句。
      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

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

        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 标识符,并且也提前知道(在编码时),第二种方法有问题。如果不是这种情况,您可能应该寻找another approach

        【讨论】:

        • 点赞。我不知道你可以写dict(mapping, **kwargs)。我以为是或。注意:它复制 os.environ 而不将其修改为 @Daniel Burke suggested in the currently accepted answer 但您的答案更简洁。在 Python 3.5+ 中,您甚至可以使用 dict(**{'x': 1}, y=2, **{'z': 3})。见pep 448
        • 这个答案解释了一些更好的方法(以及为什么这种方法不是那么好)将两个字典合并到一个新的字典中:stackoverflow.com/a/26853961/27729
        • @krupan:您认为这个特定的用例有什么缺点? (合并任意字典和复制/更新环境是不同的任务)。
        • @krupan 首先,正常情况是环境变量是有效的python标识符,这意味着第一个构造。对于这种情况,您的反对意见均不成立。对于第二种情况,您的主要反对意见仍然失败:关于非字符串键的观点在这种情况下不适用,因为键基本上必须是环境中的字符串。
        • @J.F.Sebastian 你是对的,对于这种特定情况,这种技术很好,我应该更好地解释自己。我很抱歉。我只是想帮助那些可能想采用这种技术并将其应用于合并两个任意字典的一般情况的人(比如我自己)(正如我链接到的答案所指出的那样,它有一些陷阱)。跨度>
        【解决方案7】:

        使用 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}
        

        【讨论】:

          【解决方案8】:

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

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

          【讨论】:

            猜你喜欢
            • 2011-08-05
            • 1970-01-01
            • 2011-01-04
            • 2018-12-05
            • 2014-09-15
            • 1970-01-01
            • 2012-03-03
            • 2015-03-18
            相关资源
            最近更新 更多