【问题标题】:Parse a nested variable from YAML file in bash在 bash 中解析 YAML 文件中的嵌套变量
【发布时间】: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),它没有值。

标签: bash kubernetes yaml


【解决方案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 'END{print $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
      

      【讨论】:

        猜你喜欢
        • 2018-12-21
        • 2013-05-10
        • 2020-02-03
        • 2020-07-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多