簡體   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