是否有任何模块可以读取shell脚本?

时间:2013-07-15 18:50:07

标签: python bash shell export environment-variables

我现在正在制作一个python脚本,我需要使用一些在bash shell脚本中设置的环境变量。

bash脚本类似于:

#! /bin/sh

#sets some names:
export DISTRO="unified"
#export DISTRO="other"

#number of parallel builds
export BB_NUM_THREADS=2

#set build dir
export BUILDDIR=$PWD

通常情况下,我只是用bash来源这个脚本,然后去做我的构建。我试图在整个过程中包装python来对输出进行一些管理,所以我想删除手册source ./this_script.sh步骤。

我想要做的是从python中读取此脚本,然后使用os.environ在其中设置变量。 (我知道这不会影响父级,但只影响当前运行的Python实例,这很好)

所以为了让我的工作更轻松,我试图找出是否有任何模块可以“解析”bash脚本并利用其中的环境变量?目前我手动这样做,这有点痛苦。

如果没有这样的模块可以完全我想要的东西,是否有一个更加pythonic(读取:更简单/更短)的方式手动解析文件,现在我正在这样做:

def parse_bash_script(fn):
  with open(fn) as f:
    for line in f:
      if not line[:1] == '#':   #ignore comments
        if "export" in line:
          line = line.replace(" ","").strip()
          var = line[6:line.find("=")]
          val = line[line.find("=")+1:len(line)]
          if "\"" in val:
            val = val[1:-1]
          os.environ[var]=val

3 个答案:

答案 0 :(得分:4)

没有任何模块可以完全按照您的意愿行事,但shlex会做很多你想要的事情。特别是,它将获得quoting等权利,而不必担心它(这是最难的部分),以及跳过评论等。它唯一不会做的是处理export个关键字。

简单的方法就是预处理:

with open(fn) as f:
    processed = f.read().replace('export ', '')
for line in shlex.split(processed):
    var, _, value = line.partition('=')
    os.environ[var] = val

这有点令人讨厌,但你也可以通过后期处理来减少冗长。特别是,shlex会将export foo="bar spam eggs"视为两个值:exportfoo="bar spam eggs",您可以跳过== 'export'或分区找到的值没什么,或者......例如:

with open(fn) as f:
    for line in shlex.split(f.read()):
        var, eq, value = line.partition('=')
        if eq:
            os.environ[var] = val

如果你想变得更加漂亮,你可以构造一个shlex对象和(a)直接从文件驱动解析器,以及(b)控制更细粒度级别的解析。但是,我认为这不是必要的。


同时,如果你想要处理环境替换(如BUILDDIR=$PWD所暗示的那样),那么这对你来说不会神奇地处理。您可以configparser使用ExtendedInterpolation featureconfigparser执行此操作,但是您需要欺骗shlex处理$PWD-foo语法,此时......为什么要这么麻烦。< / p>

您当然可以通过编写自己的插补器手动完成,但这很难做到。您需要了解shell的规则,为什么${PWD}-foo$PWD_foo相同,但${PWD_foo}with open('script.sh') as f: script = f.read() script += b'\nenv' with subprocess.Popen(['sh'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) as p: result = p.communicate(script) for line in result.splitlines(): var, _, value = line.partition('=') os.environ[var] = value 相同,等等。

此时更好的解决方案 - 假设脚本实际上可以安全运行 - 将实际使用shell为您执行此操作。例如:

_=/usr/bin/env

当然,这也会覆盖{{1}}之类的内容,但可能不是你关心的任何内容。

答案 1 :(得分:1)

def parse_bash_script(fn):
  with open(fn) as f:
    for line in f:
      if not line.startswith('#'):   #ignore comments
        if "export" in line:
          var, _, val = line.partition('=')
          var = var.lstrip()
          val = val.rstrip()
          if val.startswith('"'):
            vals = val.rpartition('"')
            val = vals[0][1]+vals[2]
          os.environ[var]=val

答案 2 :(得分:0)

我遇到了同样的问题,根据abarnert的建议,我决定将解决方案实现为对受限制的bash shell的子进程调用,并结合shlex。

import shlex
import subprocess

filename = '/path/to/file.conf'
o, e = subprocess.Popen(
    ['/bin/bash', '--restricted', '--noprofile', '--init-file', 
     filename, '-i', '-c', 'declare'],
    env={'PATH': ''},
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE).communicate()

if e:
    raise StandardError('conf error in {}: {}'.format(filename, e))

for token in shlex.split(o):
    parts = token.split('=', 1)
    if len(parts) == 2:
        os.environ[parts[0]] = parts[1]

受限shell的优点是它可以阻止执行shell脚本时可能发生的许多不良或恶意副作用。来自bash文档:

  

受限制的shell用于设置比标准shell更受控制的环境。它的行为与bash相同,但不允许或不执行以下操作:

     
      
  • 用cd
  • 更改目录   
  • 设置或取消设置SHELL,PATH,ENV或BASH_ENV
  • 的值   
  • 指定包含/
  • 的命令名称   
  • 指定包含/作为参数的文件名。内置命令
  •   
  • 指定包含斜杠的文件名作为hash builtin命令的-p选项的参数
  •   
  • 在启动时从shell环境导入函数定义
  •   
  • 在启动时从shell环境解析SHELLOPTS的值
  •   
  • 使用&gt;,&gt; |,&lt;&gt;,&gt;&amp;,&amp;&gt;和&gt;&gt;重定向输出重定向运算符
  •   
  • 使用exec builtin命令将shell替换为另一个命令
  •   
  • 使用-f和-d选项向enable builtin命令添加或删除内置命令
  •   
  • 使用enable builtin命令启用禁用的shell内置
  •   
  • 为命令内置命令
  • 指定-p选项   
  • 使用set + r或set + o restricted关闭限制模式。
  •