我有一个基本的采购功能:
def source(
fileName = None,
update = True
):
pipe = subprocess.Popen(". {fileName}; env".format(
fileName = fileName
), stdout = subprocess.PIPE, shell = True)
data = pipe.communicate()[0]
env = dict((line.split("=", 1) for line in data.splitlines()))
if update is True:
os.environ.update(env)
return(env)
当我尝试使用它来获取特定脚本时,我会得到以下错误:
>>> source("/afs/cern.ch/sw/lcg/contrib/gcc/4.8/x86_64-slc6/setup.sh")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 9, in source
ValueError: dictionary update sequence element #51 has length 1; 2 is required
这源于可执行文件env
:返回的以下行
BASH_FUNC_module()=() { eval `/usr/bin/modulecmd bash $*`
}
闭合链条支架位于线路51上。
应该如何以稳健、合理的方式从Python中获取Bash脚本,以避免类似这样的错误(以及你能想到的任何其他可能的错误)?
您看到的行是脚本执行以下操作的结果:
module() { eval `/usr/bin/modulecmd bash $*`; }
export -f module
也就是说,它显式导出bash
函数module
,以便子(bash)shell可以使用它
我们可以从环境变量的格式中看出,您在shellshock补丁的中间升级了bash。我不认为目前有一个补丁会生成BASH_FUNC_module()=
而不是BASH_FUNC_module%%()=
,但iirc在一系列修复过程中分发了这样一个补丁。既然事情已经稳定下来,你可能想再次升级你的bash。(如果这是剪切粘贴错误,请忽略此段。)
我们还可以看出,您系统上的/bin/sh
是bash
,假设module
函数是通过源shell脚本引入的。
也许您应该决定是否关心导出的bash函数。您想将module
导出到您正在创建的环境中,还是忽略它?下面的解决方案只是返回它在环境中找到的内容,因此它将包括module
。
简而言之,如果您要解析某个试图打印环境的shell命令的输出,您将面临三个可能的问题:
导出的函数(仅限bash),它们在shellshock补丁之前和之后看起来不同,但总是至少包含一个换行符。(它们的值总是以
() {
开头,所以很容易识别。在shell shock之后,它们的名称将是BASH_FUNC_funcname%%
,但除非你在野外找不到修补前和修补后的bashes,否则你可能不想依赖它。)导出的包含换行符的变量。
在某些情况下,导出的变量根本没有值。它们实际上有一个空字符串的值,但它们可能在环境列表中没有
=
符号,一些实用程序会在没有=
的情况下打印出来。
和往常一样,最健壮(甚至可能是最简单)的解决方案是避免解析,但我们可以求助于解析我们自己创建的格式化字符串的策略,该字符串是经过精心设计的。
我们可以使用任何可以访问环境的编程语言来产生这种输出;为了简单起见,我们可以使用python本身。我们将以一种非常简单的格式输出环境变量:变量名(必须是字母数字),后跟等号,后跟值,后跟NUL(0)字节(不能出现在值中)。类似以下内容:
from subprocess import Popen, PIPE
# The commented-out line really should not be necessary; it's impossible
# for an environment variable name to contain an =. However, it could
# be replaced with a more stringent check.
prog = ( r'''from os import environ;'''
+ r'''from sys import stdout;'''
+ r'''stdout.write(" ".join("{k}={v}".format(kv)'''
+ r''' for kv in environ.iteritems()'''
#+ r''' if "=" not in kv[0]'''
+ r''' ))'''
)
# Lots of error checking omitted.
def getenv_after_sourcing(fn):
argv = [ "bash"
, "-c"
, '''. "{fn}"; python -c '{prog}' '''.format(fn=fn, prog=prog)]
data = Popen(argv, stdout=PIPE).communicate()[0]
return dict(kv.split('=', 1) for kv in data.split(' '))
我认为通常最好直接使用bash来设置环境,然后在已经设置好的环境中调用python脚本。这利用了unix/linux的核心原则之一:子进程继承父进程的环境副本。
如果我正确地理解了你的情况,那么你有一些bash脚本,它们设置了一些你想在python脚本中使用的环境。然后,这些python脚本使用准备好的环境为更多的工具设置更多的环境。
我建议以下设置:
-
bash包装
- 使用bash脚本设置环境
- 调用您的python设置脚本(python脚本从bash脚本继承环境)
-
您当前的python脚本没有读取的子流程和环境
- 在上面bash脚本准备的环境中启动
- 继续为下一个工具准备环境
通过这种方式,您可以在每个脚本的"本地环境"中使用它们。
另一种选择是手动将bash脚本转换为python。