在 bash 中解析 YAML 文件中的嵌套变量

Posted

技术标签:

【中文标题】在 bash 中解析 YAML 文件中的嵌套变量【英文标题】:Parse a nested variable from YAML file in bash 【发布时间】:2019-07-23 00:08:06 【问题描述】:

需要将来自 this link 的复杂 .yaml 文件输入到 bash 脚本中,该脚本作为在 Amazon Linux 2 的 EC2 实例上运行的自动化程序的一部分运行。请注意,上面链接中的 .yaml 文件包含许多对象,并且我需要提取在文件中定义的众多对象之一中定义的环境变量之一。

具体来说,如何将CALICO_IPV4POOL_CIDR 变量的192.168.0.0/16 值提取到bash 变量中?

        - name: CALICO_IPV4POOL_CIDR
          value: "192.168.0.0/16"

我已经阅读了很多关于解析更扁平、更简单的.yaml 文件的其他帖子和博客条目,但是这些其他示例都没有显示如何在这个问题中提取像CALICO_IPV4POOL_CIDRvalue 这样的嵌套值。

【问题讨论】:

CALICO_IPV4POOL_CIDR 一个值(对于键name),它没有值。 【参考方案1】:

根据其他人的评论,如果可用,建议使用yq(以及jq)。 那么请尝试以下方法:

value=$(yq -r 'recurse | select(.name? == "CALICO_IPV4POOL_CIDR") | .value' "calico.yaml")
echo "$value"

输出:

192.168.0.0/16

【讨论】:

【参考方案2】:

如果您能够安装新的依赖项,并且计划处理大量 yaml 文件,yq 是 jq 的包装器,可以处理 yaml。它允许以安全(非 grep)方式访问嵌套的 yaml 值。

用法类似于MY_VALUE=$(yq '.myValue.nested.value' < config-file.yaml)

或者,How can I parse a YAML file from a Linux shell script? 有一个仅限 bash 的解析器,您可以使用它来获取您的值。

【讨论】:

【参考方案3】:

正确的做法是使用脚本语言和 YAML 解析库来提取您感兴趣的字段。

这是一个如何在 Python 中执行此操作的示例。如果您真的这样做,您可能会将其拆分为多个功能并获得更好的错误报告。这只是为了说明calico.yaml 的格式带来的一些困难,calico.yaml 是多个 YAML 文档串联在一起,而不仅仅是一个。您还必须遍历文档内部的一些列表才能提取您感兴趣的字段。

#!/usr/bin/env python3

import yaml

def foo():
    with open('/tmp/calico.yaml', 'r') as fil:
        docs = yaml.safe_load_all(fil)
        doc = None
        for candidate in docs:
            if candidate["kind"] == "DaemonSet":
                doc = candidate
                break
        else:
            raise ValueError("no YAML document of kind DaemonSet")
        l1 = doc["spec"]
        l2 = l1["template"]
        l3 = l2["spec"]
        l4 = l3["containers"]
        for containers_item in l4:
            l5 = containers_item["env"]
            env = l5
            for entry in env:
                if entry["name"] == "CALICO_IPV4POOL_CIDR":
                    return entry["value"]
    raise ValueError("no CALICO_IPV4POOL_CIDR entry")

print(foo())

但是,有时您现在就需要一个解决方案,而 shell 脚本非常擅长。

如果您要访问 API 端点,那么 YAML 通常会打印得很漂亮,这样您就可以通过在任意 YAML 上无法使用的方式提取文本。

类似下面的东西应该是相当健壮的:

cat </tmp/calico.yaml | grep -A1 CALICO_IPV4POOL_CIDR | grep value: | cut -d: -f2 | tr -d ' "'

虽然值得在末尾使用正则表达式检查提取的值确实是有效的 IPv4 CIDR 表示法。

这里的关键是grep -A1 CALICO_IPV4POOL_CIDR

您提到的双元素字典(如下所示)将始终显示为一个块,因为它是 YAML 文档的子树。

    - name: CALICO_IPV4POOL_CIDR
      value: "192.168.0.0/16"

calico.yaml 中的键通常不按字母顺序排序,但在"name": &lt;something&gt;, "value": &lt;something else&gt; 结构中,name 始终出现在value 之前。

【讨论】:

这里有两个问题:你正在使用 PyYAML 的 load_all(),它被证明是不安全的,没有理由不使用 safe_load_all(); OP 没有说那是 YAML 1.1(十年前被 YAML 1.2 取代,PyYAML 无法处理) @Anthon,谢谢。我尝试查看项目 calico 存储库,看看是否可以找到任何提及正在使用的 YAML 版本,并且没有明确记录。但是,像 off 这样的明确 YAML- 【参考方案4】:
MYVAR=$(\
curl https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml | \
grep -A 1 CALICO_IPV4POOL_CIDR | \
grep value | \
cut -d ':' -f2 | \
tr -d ' "')

curl https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml 替换为您获取文件的方式。这被传送到grep -A 1 CALICO_IPV4POOL_CIDR。这为您提供了 2 行文本:名称行和值行。它被传送到grep value,它现在为我们提供了我们想要的行,只有值。它被传送到cut -d ':' -f2,它使用冒号作为分隔符并为我们提供第二个字段。 $(...) 执行附带的脚本,并将其分配给MYVAR。在这个脚本之后,echo $MYVAR 应该产生192.168.0.0/16

【讨论】:

你可以这样做grep -A1 CALICO_IPV4POOL_CIDR | awk 'ENDprint $2'【参考方案5】:

你有两个问题:

如何从包含多个文档的文件中读取 YAML 文档 如何从该 YAML 文档中选择所需的密钥

通过阅读 Gregory Nisbett 的回答,我猜到您需要“DaemonSet”类型的 YAML 文档。

我将尝试仅使用可能已经安装在您的系统上的工具,因为您提到您希望在 Bash 脚本中执行此操作。我假设你有 JQ,因为没有它很难在 Bash 中做很多事情!

对于 YAML 库,我倾向于使用 Ruby,因为:

大多数系统都有 Ruby Ruby 的 Psych 库从 Ruby 1.9 开始捆绑 根据我的经验,与 Ruby 相比,Python 中的 PyYAML 库有点不灵活,有时会损坏 Perl 中的 YAML 库通常不会默认安装

建议使用yq,但在这种情况下不会有太大帮助,因为您仍然需要一个可以提取 YAML 文档的工具。

提取文档后,我将再次使用 Ruby 将文件保存为 JSON。然后我们就可以使用jq了。

提取 YAML 文档

使用 Ruby 获取 YAML 文档并将其保存为 JSON:

url=...
curl -s $url | \
  ruby -ryaml -rjson -e \
    "puts YAML.load_stream(ARGF.read)
      .select|doc| doc['kind']=='DaemonSet'[0].to_json" \
  | jq . > calico.json

进一步说明:

YAML.load_stream 读取 YAML 文档并将它们全部作为数组返回 ARGF.read 读取通过 STDIN 传递的文件 Ruby 的 select 允许根据类型键轻松选择 YAML 文档 然后我们将元素 4 转换为 JSON。

我通过jq . 传递该响应,以便对其进行格式化以供人类阅读,但该步骤并不是真正必要的。我可以在 Ruby 中做同样的事情,但我猜你希望 Ruby 代码保持在最低限度。

选择您想要的键

要选择您想要的键,可以使用以下 JQ 查询:

jq -r \
  '.spec.template.spec.containers[].env[] | select(.name=="CALICO_IPV4POOL_CIDR") | .value' \
  calico.json                                                          

进一步说明:

第一部分spec.template.spec.containers[].env[] 迭代所有容器和其中的所有环境 然后我们选择name key等于CALICO_IPV4POOL_CIDR的Hash并返回值 -r 删除字符串周围的引号

把它们放在一起:

#!/usr/bin/env bash

url='https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml'

curl -s $url | \
  ruby -ryaml -rjson -e \
    "puts YAML.load_stream(ARGF.read)
      .select|doc| doc['kind']=='DaemonSet'[0].to_json" \
  | jq . > calico.json

jq -r \
  '.spec.template.spec.containers[].env[] | select(.name=="CALICO_IPV4POOL_CIDR") | .value' \
  calico.json

测试:

▶ bash test.sh
192.168.0.0/16

【讨论】:

以上是关于在 bash 中解析 YAML 文件中的嵌套变量的主要内容,如果未能解决你的问题,请参考以下文章

嵌套循环结合修改IFS环境变量遍历文件数据中IFS的修改一致性

如果在树中嵌套其他值,则获取文本文件(yaml)中的键值[重复]

如果有“!”,如何使用 PyYAML 解析 YAML在 YAML 中

yaml数组类型详解

如何使用 snakeYaml 库访问 YAML 文件中的内部(嵌套)键值

bash中的变量