在Python中模拟Bash的'source
我有一个脚本,大概长这样:
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
?
6 个回答
与其让你的Python脚本去调用bash脚本,不如写一个包装脚本,先调用init_env
,然后再用修改过的环境来运行你的Python脚本,这样会更简单、更优雅。
#!/bin/bash
source init_env
/run/python/script.py
使用 pickle:
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、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
你这个方法的问题在于,你试图自己解析bash脚本。首先,你只是想解析export语句。但当人们觉得可以使用bash语法时,他们就会这样做。他们会使用变量扩展、条件语句、进程替换。最后,你就会变成一个功能齐全的bash脚本解析器,里面有无数的bug。别这样。
让Bash来帮你解析文件,然后收集结果。
下面是一个简单的例子,教你怎么做:
#! /usr/bin/env python
import os
import pprint
import shlex
import subprocess
command = shlex.split("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))
确保你处理好错误。可以看看这里了解如何处理:"subprocess.Popen" - 检查成功和错误
还要阅读一下关于subprocess的文档。
这个方法只会捕获用export
语句设置的变量,因为env
只会打印导出的变量。你可以加上set -a
,这样所有变量都会被当作导出变量处理。
command = shlex.split("bash -c 'set -a && source init_env && env'")
^^^^^^
注意,这段代码不会处理多行变量,也不会处理bash函数定义。
或许比在Python中调用bash的source命令更好的方法是,先让bash去source那个文件,然后再运行Python脚本。
#!/bin/bash
source init_env
/path/to/python_script.py
在这里,bash会用source init_env
来执行,充分利用bash的各种功能和特性。Python脚本会继承更新后的环境。
再次提醒,只有导出的变量会被继承。你可以用set -a
强制所有变量赋值都被导出。
#!/bin/bash
set -a
source init_env
/path/to/python_script.py
另一种方法是告诉用户,他们只能严格使用key=value
的格式,而不能使用bash的其他功能。然后使用Python的configparser。
这样做的好处是init_env
的语法简单,而且有经过严格测试的配置解析器。但缺点是,init_env
的表达能力就没有bash配置文件那么强大了。