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

时间:2015-02-12 00:02:41

标签: python bash dictionary env

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

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脚本,以避免这样的错误(以及您能想到的任何其他可能的错误)?

2 个答案:

答案 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/shbash,假设通过获取shell脚本引入了module函数。

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

简而言之,如果您要解析某些尝试打印环境的shell命令的输出,那么您将遇到三个可能的问题:

  1. 导出的函数(仅限bash),它们在shellshock补丁之前和之后看起来不同,但始终包含至少一个换行符。 (它们的值始终以() {开头,因此它们很容易识别。发布shellshock后,它们的名称将为BASH_FUNC_funcname%%但是直到你在野外找不到前后修补的bas,你可能不想依赖它。)

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

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

  4. 与往常一样,最强大(也可能是最简单)的解决方案是避免解析,但我们可以依靠解析我们自己创建的格式化字符串的策略,这是经过精心设计的解析。

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

我建议遵循以下设置:

  1. bash包装器

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

    • 从上面的bash脚本准备的环境开始
    • 继续为下一个工具准备环境
  3. 这样您就可以在“原生环境”中使用每个脚本。

    另一种方法是将bash脚本手动转换为python。