在Python中模拟Bash“源”


91

我有一个看起来像这样的脚本:

export foo=/tmp/foo                                          
export bar=/tmp/bar

每次构建时,我都会运行“ source init_env”(其中init_env是上面的脚本)来设置一些变量。

为了在Python中完成同样的工作,我运行了这段代码,

reg = re.compile('export (?P<name>\w+)(\=(?P<value>.+))*')
for line in open(file):
    m = reg.match(line)
    if m:
        name = m.group('name')
        value = ''
        if m.group('value'):
            value = m.group('value')
        os.putenv(name, value)

但是后来有人决定将以下行添加到init_env文件中会很好:

export PATH="/foo/bar:/bar/foo:$PATH"     

显然我的Python脚本崩溃了。我可以修改Python脚本来处理这一行,但是稍后当有人想出要在init_env文件中使用的新功能时,它将中断。

问题是,是否有一种简单的方法来运行Bash命令并让其修改我的os.environ


Answers:


109

您的方法存在的问题是您试图解释bash脚本。首先,您只是尝试解释导出语句。然后您会发现人们正在使用变量扩展。以后人们将条件文件放入他们的文件中,或处理替代文件。最后,您将获得功能齐全的bash脚本解释器,其中包含大量的错误。不要那样做

让Bash为您解释文件,然后收集结果。

您可以这样做:

#! /usr/bin/env python

import os
import pprint
import shlex
import subprocess

command = shlex.split("env -i bash -c 'source init_env && env'")
proc = subprocess.Popen(command, stdout = subprocess.PIPE)
for line in proc.stdout:
  (key, _, value) = line.partition("=")
  os.environ[key] = value
proc.communicate()

pprint.pprint(dict(os.environ))

确保处理错误,以防bash失败source init_env,bash本身无法执行,子进程无法执行bash或任何其他错误。

env -i在命令行的开始创造一个清洁的环境。这意味着您只能从中获取环境变量init_env。如果您想要继承的系统环境,请省略env -i

阅读有关子流程的文档以了解更多详细信息。

注意:这将仅捕获使用export语句设置的变量,因为env仅打印导出的变量。

请享用。

请注意,Python文档指出,如果要操作环境,则应os.environ直接操作而不是使用os.putenv()。我认为是一个错误,但我离题了。


12
如果您确实关心非导出变量,并且脚本不在您的控制范围内,则可以使用set -a将所有变量标记为已导出。只需将命令更改为:['bash','-c','set -a && source init_env && env']
2013年

请注意,这将在导出的函数上失败。如果您可以更新答案以显示对函数也适用的解析,那我将非常喜欢。(例如,函数fff(){echo“ fff”;}; export -f fff)
DA

2
注意:这不支持多行环境变量。
BenC

2
在我的情况下,迭代proc.stdout()产生字节,因此得到了TypeErroron line.partition()。转换成字符串line.decode().partition("=")解决了问题。
山姆F

1
这超级有帮助。我执行['env', '-i', 'bash', '-c', 'source .bashrc && env']了仅给自己一个由rc文件设置的环境变量的操作
xaviersjs

32

使用泡菜:

import os, pickle
# For clarity, I moved this string out of the command
source = 'source init_env'
dump = '/usr/bin/python -c "import os,pickle;print pickle.dumps(os.environ)"'
penv = os.popen('%s && %s' %(source,dump))
env = pickle.loads(penv.read())
os.environ = env

更新:

这使用json,子进程,并显式使用/ bin / bash(以支持ubuntu):

import os, subprocess as sp, json
source = 'source init_env'
dump = '/usr/bin/python -c "import os, json;print json.dumps(dict(os.environ))"'
pipe = sp.Popen(['/bin/bash', '-c', '%s && %s' %(source,dump)], stdout=sp.PIPE)
env = json.loads(pipe.stdout.read())
os.environ = env

这在Ubuntu上有问题-默认外壳程序存在/bin/dash,它不知道source命令。为了在Ubuntu上使用它,您必须/bin/bash显式地运行,例如通过使用penv = subprocess.Popen(['/bin/bash', '-c', '%s && %s' %(source,dump)], stdout=subprocess.PIPE).stdout(这将使用subprocess必须导入的较新模块)。
马丁·佩卡

22

而不是让您的Python脚本作为bash脚本的源,而是拥有一个包装器脚本的源init_env,然后在修改后的环境中运行Python脚本会更简单,更优雅。

#!/bin/bash
source init_env
/run/python/script.py

4
它可以在某些情况下解决问题,但不能全部解决。例如,我正在编写一个python脚本,该脚本需要执行诸如获取文件的操作(实际上,如果您知道我在说什么,它将加载模块),并且它需要根据某些情况加载其他模块。因此,这不会解决我的所有问题
达维德

在大多数情况下,这确实可以回答该问题,并且我会尽可能使用它。我很难在给定项目的IDE中完成这项工作。一种可能的修改是将整个事情与环境一起在shell中运行bash --rcfile init_env -c ./script.py
xaviersjs

6

更新了@lesmana对Python 3的回答。请注意,使用env -i它可以防止设置/重置无关的环境变量(由于缺乏对多行env变量的处理,因此可能会错误地进行设置)。

import os, subprocess
if os.path.isfile("init_env"):
    command = 'env -i sh -c "source init_env && env"'
    for line in subprocess.getoutput(command).split("\n"):
        key, value = line.split("=")
        os.environ[key]= value

使用此命令会给我“ PATH:未定义的变量”,因为env -i会取消设置路径。但它确实可以在没有env -i的情况下工作。还要注意,该行可能有多个'='
Fujii

4

将@Brian的出色答案包装在函数中的示例:

import json
import subprocess

# returns a dictionary of the environment variables resulting from sourcing a file
def env_from_sourcing(file_to_source_path, include_unexported_variables=False):
    source = '%ssource %s' % ("set -a && " if include_unexported_variables else "", file_to_source_path)
    dump = '/usr/bin/python -c "import os, json; print json.dumps(dict(os.environ))"'
    pipe = subprocess.Popen(['/bin/bash', '-c', '%s && %s' % (source, dump)], stdout=subprocess.PIPE)
    return json.loads(pipe.stdout.read())

我正在使用此实用程序功能来读取AWS凭证和docker .env文件include_unexported_variables=True

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.