【问题标题】:Are there any modules to read shell scripts?是否有任何模块可以读取 shell 脚本?
【发布时间】:2013-07-13 18:07:59
【问题描述】:

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

【问题讨论】:

  • 脚本真的只是 cmets 和 export var=value 行,还是里面有其他东西?
  • 特别是,在仔细阅读本文之后……您是否需要像 shell 一样评估 BUILDDIR=$PWD?换句话说,BUILDDIR 应该以类似/Users/Mike/src/testing 的形式结束,还是以文字形式的$PWD
  • @abarnert - 正如你所说,里面还有其他东西。我只是要为“特殊情况”更新我的 parse_bash_script() 方法,例如 BUILDDIR=$PWD... 在我走这条路之前,我想看看我是否错过了一些更好的选择
  • 好的,所以下一个问题是:只执行 sh 脚本是否安全?如果是这样,那可能是最简单的答案。正确处理变量 sh 样式的替换(特别是如果您必须根据 shebang 行指定 sh 还是 bash 来处理它不同的事实)比正确处理引用更难。如果你想完全通用,你打算用foo=$(ls)这样的东西做什么?
  • @abarnert - 是的,只执行该脚本是安全的,这将是我的首选方法......在我看来这是不可能的。我尝试了sourceing 它和使用Popen 的其他一些选项,但我似乎无法做到这一点。我是否错过了在我的 Python 环境中“获取”这个脚本的简单方法?

标签: python bash shell export environment-variables


【解决方案1】:

没有模块可以完全满足您的需求,但shlex 会满足您的需求。特别是,它会得到quoting等,而不用担心它(这是最难的部分),以及跳过cmets等。它唯一不会做的就是处理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 feature 为你做这件事,但是你需要欺骗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 之类的内容,但可能不是您关心的任何内容。

【讨论】:

  • 理想情况下,我想让 shell 运行一些东西,但是当我执行 with ...result = p.comm... 行时,我会返回 "Traceback (most recent call last): File '<stdin>', line 1, in <module> Attribute Error: __exit__"。在我看来,子进程不喜欢运行 shell 脚本
  • 另外,subprocess.Popen(['sh'], 不只是在我的 Python 会话中打开一个新的 shell,它本身就在一个 shell 中?我认为'sh' shell 中的变量不会在我的 python 会话中“设置”
  • @Mike:这并不意味着子进程不喜欢运行 shell 脚本;这意味着 Python 不喜欢 Popen 作为上下文管理器(又名“with 语句中的东西”)。你使用的是什么版本的 Python?正如the docs 所说,在 Python 3.2 中添加了上下文管理器支持。在旧版本中,您必须执行p = subprocess.Popen(…),然后确保正确关闭所有内容。但除此之外,它是一样的。
  • @Mike:是的,Popen(['sh']) 将打开一个新的 shell 作为 Python 解释器的子进程。这不会自动影响 Python 解释器(或其任何子进程)中的变量。这就是为什么我将env 添加到脚本的末尾,并读取并解析它的输出。
【解决方案2】:
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

【讨论】:

  • 这不一定能正确处理引用,OP 的代码明确地做了(虽然只是部分成功),所以大概他需要它。例如,shell 将处理字符串中间的双引号。
  • 首先,这实际上仍然不能正确处理引用 - 尽管至少它与 OP 的原始代码完全相同,而不是引入不同的错误。其次,您不应该在那里拨打 strip 电话。 也许 var.lstrip()val.rstrip(),但不是stripfoo = bar不是在 sh 中的赋值。
  • 虽然我同意你对strip 的批评,但我看不出保留空格(在= 的两侧)会有什么帮助。 os.environ 真的会尊重这些空间吗?
  • 是的,os.environ 会尊重空格; os.environ['foo '] = ' bar' 不会覆盖os.environ['foo'],如果你稍后将subprocess 换成别的东西,它只会继承几乎无法使用的变量'foo ',而不是容易意外使用的'foo'
  • 同样,var = val 不是sh 中的变量赋值。 varnot 在 shell 中可用,因此它不应该在 Python 中可用。 (这行的意思是运行以var命名的程序,以=val作为它的两个参数。这是你可以在shell中执行foo=bar spam=eggs baz的部分原因,这对于构建脚本。)所以,把它变成var=val 是个坏主意。留在空格中很麻烦,但至少更好,因为变量'var ' 几乎不可能从子shell 或其他子进程访问,所以它是无害的。
【解决方案3】:

我遇到了同样的问题,根据 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 内置命令添加或删除内置命令
  • 使用 enable builtin 命令启用禁用的 shell 内置函数
  • 为命令内置命令指定 -p 选项
  • 使用 set +r 或 set +o restricted 关闭受限模式。

【讨论】:

    猜你喜欢
    • 2021-07-07
    • 2017-11-18
    • 2015-03-20
    • 2013-08-04
    • 1970-01-01
    • 2019-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多