繁体   English   中英

运行子进程后如何获取环境变量

[英]How to get environment variables after running a subprocess

我正在使用subprocess.call从我正在集成的另一个应用程序中执行 shell 脚本。 此脚本使用export MY_VAR=foo设置环境变量。 接下来,我需要使用 shell 脚本设置的环境在子进程上执行更多命令。

如何从子进程中提取环境状态? 它只返回errno代码。

即我想运行:

subprocess.call(["export", "MY_VAR=foo"]
subprocess.call(["echo", "$MY_VAR"])  # should print 'foo'.

我知道我可以使用env关键字设置环境,但我的问题的重点是如何获取子进程设置的环境变量。 在外壳可以source任何脚本得到它的声明环境变量。 python中的替代方案是什么?

这是不可能的,因为环境只在子进程中改变。 您可能会从那里将其作为输出返回到 STDOUT、STDERR - 但是一旦子进程终止,您就无法从中访问任何内容。

# this is process #1
subprocess.call(["export", "MY_VAR=foo"]

# this is process #2 - it can not see the environment of process #1
subprocess.call(["echo", "$MY_VAR"])  # should print 'foo'.

我最近遇到了这个问题。 由于 Python 上游的原因,这似乎是一个难题: posix_spawn没有提供读取生成进程的环境变量的方法, 也没有任何简单的方法来读取正在运行的进程的环境

Bash 的source特定于在 bash 解释器中运行 bash 代码:它只是在当前 bash 解释器中评估文件,而不是启动子进程。 如果您从 Python 运行 bash 代码,则此机制无法工作。

可以创建一个专门用于从 Python 运行 bash 代码的单独机制。 以下是我能管理的最好的。 有一个不那么脆弱的解决方案会很好。

import json
import os
import subprocess
import sys

from contextlib import AbstractContextManager


class BashRunnerWithSharedEnvironment(AbstractContextManager):
    """Run multiple bash scripts with persisent environment.

    Environment is stored to "env" member between runs. This can be updated
    directly to adjust the environment, or read to get variables.
    """

    def __init__(self, env=None):
        if env is None:
            env = dict(os.environ)
        self.env: Dict[str, str] = env
        self._fd_read, self._fd_write = os.pipe()

    def run(self, cmd, **opts):
        if self._fd_read is None:
            raise RuntimeError("BashRunner is already closed")
        write_env_pycode = ";".join(
            [
                "import os",
                "import json",
                f"os.write({self._fd_write}, json.dumps(dict(os.environ)).encode())",
            ]
        )
        write_env_shell_cmd = f"{sys.executable} -c '{write_env_pycode}'"
        cmd += "\n" + write_env_shell_cmd
        result = subprocess.run(
            ["bash", "-ce", cmd], pass_fds=[self._fd_write], env=self.env, **opts
        )
        self.env = json.loads(os.read(self._fd_read, 5000).decode())
        return result

    def __exit__(self, exc_type, exc_value, traceback):
        if self._fd_read:
            os.close(self._fd_read)
            os.close(self._fd_write)
            self._fd_read = None
            self._fd_write = None
    
    def __del__(self):
        self.__exit__(None, None, None)

例子:

with BashRunnerWithSharedEnvironment() as bash_runner:
    bash_runner.env.pop("A", None)

    res = bash_runner.run("A=6; echo $A", stdout=subprocess.PIPE)
    assert res.stdout == b'6\n'
    assert bash_runner.env.get("A", None) is None

    bash_runner.run("export A=2")
    assert bash_runner.env["A"] == "2"

    res = bash_runner.run("echo $A", stdout=subprocess.PIPE)
    assert res.stdout == b'2\n'

    res = bash_runner.run("A=6; echo $A", stdout=subprocess.PIPE)
    assert res.stdout == b'6\n'
    assert bash_runner.env.get("A", None) == "6"


    bash_runner.env["A"] = "7"
    res = bash_runner.run("echo $A", stdout=subprocess.PIPE)
    assert res.stdout == b'7\n'
    assert bash_runner.env["A"] == "7"

不确定我在这里看到了问题。 您只需要记住以下几点:

  • 每个启动的子流程都独立于之前子流程中所做的任何设置
  • 如果你想设置一些变量并使用它们,在一个过程中做这两个事情

所以让setupVars.sh像这样:

export vHello="hello"
export vDate=$(date)
export vRandom=$RANDOM

并使printVars.sh像这样:

#!/bin/bash
echo $vHello, $vDate, $vRandom

并使用以下命令制作该可执行文件:

chmod +x printVars.sh

现在你的 Python 看起来像这样:

import subprocess

subprocess.call(["bash","-c","source setupVars.sh; ./printVars.sh"])

输出

hello, Mon Jul 12 00:32:29 BST 2021, 8615

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM