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

时间:2015-11-10 16:53:00

标签: python subprocess environment-variables

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

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

但是一旦我使用os.putenv或运行任何修改了环境的ctypes代码,我就会在实际的流程环境和os.environ之间产生不一致。 Nuff说,无论是用os.system还是subprocess库创建的,都会为子进程保留这个实际环境。在我看来,这是他们想要的行为。

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

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

2 个答案:

答案 0 :(得分:3)

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)related Python issue@ShadowRanger has mentioned

如果你需要它;您可以使用ctypes从Python访问C environ,例如(在Ubuntu上测试,它可能适用于OS X(您可能需要调用_NSGetEnviron() there),它不太适用于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。

答案 1 :(得分:2)

这是StructTypeos.getenvos.environ读取,并在os.environ上设置项隐式执行os.putenv,删除隐式调用os.unsetenv等。

但即使os.getenvos.environ读取,os.putenv也不会写入(known issue with Python, as yet unfixed)。并且this behavior is documented。基本上,如果您想要一致的环境,则必须仅更新os.environ,而不是使用os.putenv;如果ctypes来电直接更新C级environ,则您需要进行另一次ctypes调用才能阅读C级environ并更新os.environ匹配。