2

我现在正在制作一个 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
4

3 回答 3

4

没有模块可以完全按照您的要求做,但shlex会做很多您想做的事。特别是,它会得到正确的引用等,而您不必担心它(这是最难的部分),以及跳过评论等。它唯一不会做的就是处理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

它有点hackier,但你也可以通过后期处理来减少冗长。特别是,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功能为您做到这一点,但是您需要欺骗configparser处理shlex语法,此时……何必费心呢。

您当然可以通过编写自己的插值器来手动完成,但这很难做到。您需要知道 shell 的规则,为什么$PWD-foo与 相同${PWD}-foo,但$PWD_foo与 相同${PWD_foo},等等。

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

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

当然,这也会覆盖类似的东西_=/usr/bin/env,但可能不是你关心的任何东西。

于 2013-07-15T19:00:24.807 回答
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
于 2013-07-15T18:54:14.490 回答
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 内置命令的 -p 选项的参数
  • 在启动时从 shell 环境中导入函数定义
  • 在启动时从 shell 环境中解析 SHELLOPTS 的值
  • 使用 >、>|、<>、>&、&> 和 >> 重定向运算符重定向输出
  • 使用 exec 内置命令将 shell 替换为另一个命令
  • 使用 -f 和 -d 选项向 enable builtin 命令添加或删除内置命令
  • 使用 enable builtin 命令启用禁用的 shell 内置命令
  • 为命令内置命令指定 -p 选项
  • 使用 set +r 或 set +o 限制关闭受限模式。
于 2016-08-10T14:36:32.523 回答