从Python中获取Bash脚本的好方法是什么



我有一个基本的采购功能:

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/shbash,假设module函数是通过源shell脚本引入的。

也许您应该决定是否关心导出的bash函数。您想将module导出到您正在创建的环境中,还是忽略它?下面的解决方案只是返回它在环境中找到的内容,因此它将包括module

简而言之,如果您要解析某个试图打印环境的shell命令的输出,您将面临三个可能的问题:

  1. 导出的函数(仅限bash),它们在shellshock补丁之前和之后看起来不同,但总是至少包含一个换行符。(它们的值总是以() {开头,所以很容易识别。在shell shock之后,它们的名称将是BASH_FUNC_funcname%%,但除非你在野外找不到修补前和修补后的bashes,否则你可能不想依赖它。)

  2. 导出的包含换行符的变量。

  3. 在某些情况下,导出的变量根本没有值。它们实际上有一个空字符串的值,但它们可能在环境列表中没有=符号,一些实用程序会在没有=的情况下打印出来。

和往常一样,最健壮(甚至可能是最简单)的解决方案是避免解析,但我们可以求助于解析我们自己创建的格式化字符串的策略,该字符串是经过精心设计的。

我们可以使用任何可以访问环境的编程语言来产生这种输出;为了简单起见,我们可以使用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脚本使用准备好的环境为更多的工具设置更多的环境。

我建议以下设置:

  1. bash包装

    • 使用bash脚本设置环境
    • 调用您的python设置脚本(python脚本从bash脚本继承环境)
  2. 您当前的python脚本没有读取的子流程和环境

    • 在上面bash脚本准备的环境中启动
    • 继续为下一个工具准备环境

通过这种方式,您可以在每个脚本的"本地环境"中使用它们。

另一种选择是手动将bash脚本转换为python。

相关内容

  • 没有找到相关文章

最新更新