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

red*_*dot 6 python subprocess environment-variables

好吧,似乎环境变量的情况在python中不一致。

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

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

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

有没有办法克服这个问题?严格来说,有没有办法重新加载 os.environ 或使用其他工具获取具有实际环境的副本?

jfs*_*jfs 7

os.putenv()不会os.environ按照其文档明确说明进行更新。C putenv()(在 CPython 扩展模块中)也不会更新os.environ(如文档所述:os导入后环境中的更改未反映在 中os.environ)。

os.getenv(var)只是os.environ.get(var). 正如@ShadowRanger 提到的那样,存在相关的 Python 问题

如果你需要它;您可以使用例如从 Python访问C 环境ctypes(在 Ubuntu 上测试,它可能在 OS X 上工作(您可能需要在_NSGetEnviron()那里调用),它不太可能在 Windows 上工作(在那里使用_wenviron):

import ctypes

libc = ctypes.CDLL(None)
environ = ctypes.POINTER(ctypes.c_char_p).in_dll(libc, 'environ')
Run Code Online (Sandbox Code Playgroud)

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

for envvar in iter(iter(environ).next, None):
    print envvar
Run Code Online (Sandbox Code Playgroud)

输出

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
...
Run Code Online (Sandbox Code Playgroud)

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

env = dict(envvar.split(b'=', 1) for envvar in iter(iter(environ).next, None))
Run Code Online (Sandbox Code Playgroud)

与 同步os.environ

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

这里有几个方便的功能:

#!/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())
Run Code Online (Sandbox Code Playgroud)

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