我有一个基本的采购功能:
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脚本,以避免这样的错误(以及您能想到的任何其他可能的错误)?
答案 0 :(得分:1)
您看到的行是脚本执行以下操作的结果:
module() { eval `/usr/bin/modulecmd bash $*`; }
export -f module
也就是说,它显式地导出bash
函数module
,以便sub(bash)shell可以使用它。
我们可以从环境变量的格式中看出,您在shellshock补丁的中间升级了bash。我不认为有一个当前的补丁会生成BASH_FUNC_module()=
而不是BASH_FUNC_module%%()=
,但是iirc在修复过程中分发了这样的补丁。在事情已经解决之后,您可能希望再次升级bash。 (如果这是剪切和粘贴错误,请忽略此段。)
我们还可以告诉您系统上的/bin/sh
是bash
,假设通过获取shell脚本引入了module
函数。
您可能应该决定是否关心导出的bash函数。是否要将module
导出到您正在创建的环境中,或者只是忽略它?下面的解决方案只返回它在环境中找到的内容,因此它将包含module
。
简而言之,如果您要解析某些尝试打印环境的shell命令的输出,那么您将遇到三个可能的问题:
导出的函数(仅限bash),它们在shellshock补丁之前和之后看起来不同,但始终包含至少一个换行符。 (它们的值始终以() {
开头,因此它们很容易识别。发布shellshock后,它们的名称将为BASH_FUNC_funcname%%
但是直到你在野外找不到前后修补的bas,你可能不想依赖它。)
导出包含换行符的变量。
在某些情况下,导出的变量根本没有任何值。这些实际上具有空字符串的值,但是它们可能在没有=
符号的环境列表中,并且一些实用程序将在没有=
的情况下将它们打印出来。
与往常一样,最强大(也可能是最简单)的解决方案是避免解析,但我们可以依靠解析我们自己创建的格式化字符串的策略,这是经过精心设计的解析。
我们可以使用任何可以访问环境的编程语言来生成此输出;为简单起见,我们可以使用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("\0".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('\0'))
答案 1 :(得分:0)
我认为通常最好直接使用bash来设置环境,然后在已经设置的环境中调用python脚本。这是利用unix / linux核心原则之一:子进程继承了父进程环境的副本。
如果我理解了你的情况,那么你有一些bash脚本可以设置你想要在python脚本中拥有的环境。然后,那些python脚本使用该准备好的环境为更多工具设置更多环境。
我建议遵循以下设置:
bash包装器
您当前的python脚本没有子进程和环境读取
这样您就可以在“原生环境”中使用每个脚本。
另一种方法是将bash脚本手动转换为python。