在Python中模拟Bash'source'

get*_*kha 81 python bash

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

export foo=/tmp/foo                                          
export bar=/tmp/bar
Run Code Online (Sandbox Code Playgroud)

每次我构建时都运行'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)
Run Code Online (Sandbox Code Playgroud)

但后来有人决定在init_env文件中添加如下行:

export PATH="/foo/bar:/bar/foo:$PATH"     
Run Code Online (Sandbox Code Playgroud)

显然我的Python脚本崩溃了.我可以修改Python脚本来处理这一行,但是当有人想出一个在init_env文件中使用的新功能时,它就会破坏.

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

les*_*ana 99

您的方法的问题是您正在尝试解释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))
Run Code Online (Sandbox Code Playgroud)

确保在bash失败的情况下处理错误source init_env,或者bash本身无法执行,或者子进程无法执行bash或任何其他错误.

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

有关更多详细信息,请阅读有关子进程的文档

注意:这只会捕获使用该export语句设置的变量,因为env只打印导出的变量.

请享用.

请注意,Python文档说如果你想操纵环境,你应该os.environ直接操作而不是使用os.putenv().我认为这是一个错误,但我离题了.

  • 如果您关心非导出变量且脚本不在您的控件之内,则可以使用set -a将所有变量标记为已导出.只需将命令更改为:['bash',' - c','set -a && source init_env && env'] (11认同)
  • 注意:这不支持多行环境变量。 (2认同)
  • 在我的例子中,迭代`proc.stdout()`会产生字节,因此我在`line.partition()`上得到一个`TypeError`.使用`line.decode()转换为字符串.class("=")`解决了问题. (2认同)

Bri*_*ian 27

使用泡菜:

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

更新:

这使用json,subprocess,并显式使用/ 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
Run Code Online (Sandbox Code Playgroud)


Joh*_*ica 20

不是让你的Python脚本源于bash脚本,而是拥有一个包装器脚本源init_env,然后使用修改后的环境运行Python脚本会更简单,更优雅.

#!/bin/bash
source init_env
/run/python/script.py
Run Code Online (Sandbox Code Playgroud)

  • 这解决了问题,但没有回答问题:) (11认同)
  • 它可以在某些情况下解决问题,但不是全部。例如,我正在编写一个python脚本,该脚本需要做*源文件*的工作(实际上,如果您知道我在说什么,它会加载模块),并且它需要根据某些情况加载*不同*模块。所以这根本解决不了我的问题 (2认同)

Al *_*hri 5

更新了@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
Run Code Online (Sandbox Code Playgroud)


JDi*_*teo 5

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

我正在使用此实用程序函数读取 aws 凭据和 docker .env 文件include_unexported_variables=True