python:获取实际的环境变量来修改并传递给子进程 [英] python: getting actual environment variables to modify and pass to subprocess

查看:583
本文介绍了python:获取实际的环境变量来修改并传递给子进程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



使用 os.environ读取环境变量并不是一个秘密,而是环境变量的情况在python中不一致。 os.getenv 返回当前 os 模块导入的env状态。仍然可以使用赋值给 os.environ 键更新环境。



但是一旦我使用 os.putenv 或运行任何已修改环境的ctypes代码,我将实际的进程环境与 os.environ 之间的不一致。 Nuff表示,这个实际环境将被保留为子进程,无论是使用 os.system 还是子进程库创建。在我的情况下,这是需要的行为。



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



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

解决方案

os.putenv()不将 os.environ 更新为其文档明确表示。 C code> putenv()(在CPython扩展模块中)也不会更新 os.environ (如下所示: os 导入之后的环境未反映在 os.environ 中)



os .getenv(var)只是 os.environ.get(var) 相关的Python问题 @ ShadowRanger已经提到了



如果你需要它;您可以使用<$ c $访问Python中的 C environ c> ctypes eg (在Ubuntu上测试,它可能适用于OS X(您可能需要调用 _NSGetEnviron() there ),它不太可能在Windows上工作(使用 _wenviron there)):

  import ctypes 

libc = ctypes.CDLL(无)
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()#注意:它清除C环境也! 
getattr(os,'environb',os.environ).update(env)#单源Python 2/3兼容。

这里有几个方便的功能:

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


_environ =无


def get_libc_environb_items():
从C环境中获取envvars作为bytestrings(在b'='上取消分配)
全局_environ
如果_environ为无:
libc = ctypes.CDLL(无)
_environ = ctypes.POINTER(ctypes.c_char_p).in_dll(libc,'environ')
返回iter(functools.partial(next,iter(_environ)),无)


def get_libc_environb():
获取C环境的副本作为关键字, get_libc_environb_items()
如果b'='在k_v中)#如果CPython $ b $($)
返回dict(k_v.split(b'=',1) b


def get_libc_environ():
获取C环境的副本作为键,值映射字符串。
environb = get_libc_enviro nb()
#XXX sys.getfilesystemencoding()+'surrogateescape'
fsdecode = getattr(os,'fsdecode',无)
如果fsdecode为无:#Python 2
返回environb#keep bytestrings as((`str` type)
else:#Python 3,解码为Unicode
return {fsdecode(k):fsdecode(v)for k,v in environb.items )


def synchronize_environ():
同步os.environ与C环境。
libc_environ = get_libc_environ()
os.environ.clear()
os.environ.update(libc_environ)


def test():
assert'SPAM'不在os.environ
断言'SPAM'不在get_libc_environ()
os.putenv('SPAM','egg')
assert'SPAM'不在os.environ
assert os.getenv( 'SPAM')是无
assert get_libc_environ()['SPAM'] =='egg'
assert os.popen('echo $ SPAM')。read()。strip()==' egg'
synchronize_environ()
屁股ert os.environ ['SPAM'] =='egg'


如果__name__ ==__main__:
test()
from pprint import pprint
pprint(get_libc_environ())

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


Well, it seems situation with environment variables is not consistent in python.

It's not a secret that reading environment variables using os.environ or os.getenv returns the state of env at the moment os module was imported. It's still possible to update environment using assignment to os.environ keys.

But once I used os.putenv or run any ctypes code that has modified the environment I get inconsistency between actual process environment and os.environ. Nuff said, this actual environment will be preserved for subprocess, no matter created with os.system or subprocess library. In my case it's desired behavior.

Now I want to review and change the environment passed to subprocesses. Usually it's suggested to get copy of os.environ, modify it and pass as a parameter to subprocess.Popen call. But in this case updates made to environment made by ctypes code will be lost.

Is there any way to overcome this issue? Strictly speaking is there a way to reload os.environ or get a copy with actual environment using other facilities?

解决方案

os.putenv() does not update os.environ as its docs say explicitly. C putenv() (in a CPython extension module) does not update os.environ too (as documented: changes in the environment after os import are not reflected in os.environ).

os.getenv(var) is just os.environ.get(var). There is related Python issue as @ShadowRanger has mentioned.

If you need it; you could access 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 is a pointer to an array of C (NUL-terminated) strings (char*) where the last item is NULL. To enumerate values in Python 2:

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

Output

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
...

To get it as a dictionary that you could modify and pass to a child process:

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

To synchronize with os.environ:

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

Here're several convenience functions:

#!/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())

It works on CPython 2, CPython 3, pypy. It doesn't work on Jython.

这篇关于python:获取实际的环境变量来修改并传递给子进程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆