python:获取实际环境变量以修改并传递给子进程

python: getting actual environment variables to modify and pass to subprocess

嗯,环境变量的情况似乎与 python 不一致。

使用 os.environos.getenv returns 导入 os 模块时 env 的状态读取环境变量已经不是什么秘密了。仍然可以使用分配给 os.environ 键来更新环境。

但是一旦我使用 os.putenv 或 运行 任何修改了环境的 ctypes 代码,我就会发现实际进程环境与 os.environ 之间存在不一致。 Nuff 说,无论使用 os.system 还是 subprocess 库创建,这个实际环境都会为子进程保留。就我而言,这是理想的行为。

现在我想查看和更改传递给子流程的环境。通常建议获取 os.environ 的副本,修改它并作为参数传递给 subprocess.Popen 调用。但在这种情况下,由 ctypes 代码对环境所做的更新将会丢失。

有什么办法可以解决这个问题吗?严格来说,有没有办法重新加载 os.environ 或使用其他设施获得具有实际环境的副本?

这是一个known issue with Python, as yet unfixedos.getenvos.environ 读取,并在 os.environ 上设置项目隐式执行 os.putenv,删除隐式调用 os.unsetenv,等等

但是即使 os.getenvos.environ 读取,os.putenv 也不会写入(和 this behavior is documented). And there doesn't appear to be a way to make it reread the os.environ)。基本上,如果你想要一个一致的环境,你必须只更新 os.environ,而不是使用 os.putenv;如果 ctypes 调用直接更新 C 级别 environ,您将需要另一个 ctypes 调用读取 C 级别 environ 并更新 os.environ 以匹配。

os.putenv() 不会将 os.environ 更新为 its docs say explicitly。 C putenv()(在 CPython 扩展模块中)也不会更新 os.environ(如文档所述:os 导入后环境的变化不会反映在 os.environ).

os.getenv(var) is just os.environ.get(var). There is related Python issue as .

如果您需要;您可以访问 C environ from Python using ctypes e.g. (tested on Ubuntu, it might work on OS X (you might need to call _NSGetEnviron() there), it is unlikely to work on Windows (use _wenviron there)):

import ctypes

libc = ctypes.CDLL(None)
environ = ctypes.POINTER(ctypes.c_char_p).in_dll(libc, 'environ')

environ 是指向 C(NUL 终止)字符串数组 (char*) 的指针,其中最后一项是 NULL。枚举 Python 中的值 2:

for envvar in iter(iter(environ).next, None):
    print envvar

输出

LC_PAPER=en_GB.UTF-8
LC_ADDRESS=en_GB.UTF-8
CLUTTER_IM_MODULE=xim
LC_MONETARY=en_GB.UTF-8
VIRTUALENVWRAPPER_PROJECT_FILENAME=.project
SESSION=ubuntu
...

要将其作为您可以修改并传递给子进程的字典获取:

env = dict(envvar.split(b'=', 1) for envvar in iter(iter(environ).next, None))

os.environ同步:

os.environ.clear() # NOTE: it clears C environ too!
getattr(os, 'environb', os.environ).update(env) # single source Python 2/3 compat.

这里有几个方便的函数:

#!/usr/bin/env python
import ctypes
import functools
import os


_environ = None


def get_libc_environb_items():
    """Get envvars from C environ as bytestrings (unsplit on b'=')."""
    global _environ
    if _environ is None:
        libc = ctypes.CDLL(None)
        _environ = ctypes.POINTER(ctypes.c_char_p).in_dll(libc, 'environ')
    return iter(functools.partial(next, iter(_environ)), None)


def get_libc_environb():
    """Get a copy of C environ as a key,value mapping of bytestrings."""
    return dict(k_v.split(b'=', 1) for k_v in get_libc_environb_items()
                if b'=' in k_v)  # like CPython



def get_libc_environ():
    """Get a copy of C environ as a key,value mapping of strings."""
    environb = get_libc_environb()
    # XXX sys.getfilesystemencoding()+'surrogateescape'
    fsdecode = getattr(os, 'fsdecode', None)
    if fsdecode is None:  # Python 2
        return environb  # keep bytestrings as is (`str` type)
    else:  # Python 3, decode to Unicode
        return {fsdecode(k): fsdecode(v) for k, v in environb.items()}


def synchronize_environ():
    """Synchronize os.environ with C environ."""
    libc_environ = get_libc_environ()
    os.environ.clear()
    os.environ.update(libc_environ)


def test():
    assert 'SPAM' not in os.environ
    assert 'SPAM' not in get_libc_environ()
    os.putenv('SPAM', 'egg')
    assert 'SPAM' not in os.environ
    assert os.getenv('SPAM') is None
    assert get_libc_environ()['SPAM'] == 'egg'
    assert os.popen('echo $SPAM').read().strip() == 'egg'
    synchronize_environ()
    assert os.environ['SPAM'] == 'egg'


if __name__ == "__main__":
    test()
    from pprint import pprint
    pprint(get_libc_environ())

适用于CPython 2,CPython 3,pypy。它不适用于 Jython。