首发于 磐创AI
在命令行中掌握YAML处理

在命令行中掌握YAML处理

作者|Martin Heinz
编译|VK
来源|Towards Data Science
原文链接: towardsdatascience.com/

如今,YAML几乎用于配置所有内容(无论好坏),因此,无论你是使用Kubernetes或Ansible的DevOps工程师,还是使用GitHub操作配置Python或CI/CD登录的开发人员,你都必须至少不时地处理YAML文件。

因此,能够高效地查询和操作YAML是我们所有工程师的基本技能。

了解这一点的最佳方法是掌握YAML处理工具,如yq,它可以使你在许多日常任务中更加高效,从简单的查找到复杂的操作。所以,让我们来了解一下yq提供的所有功能,包括遍历、选择、排序等等!

设置

在开始使用yq之前,我们首先需要安装它。当你在谷歌上搜索yq时,你会发现两个项目/存储库。

首先,在 github.com/kislyuk/yq 是jq(JSON处理器)的包装器。如果你已经熟悉jq,你可能想抓住这个,使用你已经知道的语法。

不过,在本文中,我们将使用 github.com/mikefarah/yq . 这个版本没有100%匹配jq语法,但它的优点是它没有依赖性(不依赖于jq),有关差异的更多上下文,请参阅下面的GitHub问题。

github.com/mikefarah/yq

要安装它,请转到文档并选择适合你的系统的安装方法,只需确保你安装了版本4,因为这是我们将在这里使用的。最重要的是,你可能需要设置shell完成,有关信息可在 mikefarah.gitbook.io/yq .

现在我们已经安装了它,我们还需要一些YAML文件或文档来测试我们将要运行的命令。

为此,我们将使用以下文件,这些文件包含YAML中的所有常见内容—属性(普通和嵌套)、数组和各种值类型(字符串、整数和布尔值):

# log.yaml
timestamp: 1620197250
user: root
  level: fatal
  message: |
    Application exited with code 2
# user.yaml
user:
  name: John
  surname: Smith
  gender: male
  active: true
  addresses:
    - street: "Oxford Street"
      city: London
      country: England
    - street: "Ludwigstrasse"
      city: Munich
      country: Germany
  orders:
    - 4356436
    - 4345753
    - 2345234

有了这些,让我们来学习基础知识吧!

基础知识

我们将要运行的所有命令都将以相同的yq eval开始,然后是引号中的表达式和YAML文件。

这种情况的一个例外是YAML文件的打印,在这种情况下,如果你熟悉jq,你将省略表达式-例如yq eval some.yaml-那么这相当于cat some.json | jq

或者,我们也可以附加一些额外的标志,一些更有用的标志是-C强制彩色输出,-in将输出缩进设置为n个空格,或者-P用于漂亮的打印。

至于基本表达式,我们可以做很多事情,但最常见的是遍历YAML,或者换句话说,在YAML文档中查找一些键。这是通过使用.(dot)运算符,最基本的形式如下:

# yq eval '.user.addresses' user.yaml
- street: "Oxford Street"
  city: London
  country: England
- street: "Ludwigstrasse"
  city: Munich
  country: Germany

除了基本的地图导航外,你通常还需要查找数组中的特定索引(使用[N]):

# yq eval ".user.addresses[1]" user.yaml
street: "Ludwigstrasse"
city: Munich
country: Germany

最后,你可能还会发现splat操作符非常有用,它可以展平(请注意与我们看到的第一个示例的区别):

# yq eval ".user.addresses[]" user.yaml
street: "Oxford Street"
city: London
country: England
street: "Ludwigstrasse"
city: Munich
country: Germany

除了基本遍历之外,你可能还需要熟悉选择,它允许你通过布尔表达式进行过滤。为此,我们使用select(.==“此处的某些模式”)。这里,简单的示例可以是基于前导数字的过滤:

yq eval '.user.orders[] | select(. == "43*")' user.yaml
4356436
4345753

此示例还显示了管道(|)的用法-我们使用它首先导航到要筛选的文档部分,然后将其传递给select(…)。

在上面的示例中,我们使用==查找与模式相等的字段,但你也可以使用!=去匹配那些不相等的。此外,你可以完全省略select,你将只获得匹配的结果:

yq eval '.user.orders[] | (. != "43*")' user.yaml
false
false

无论你是yq的新手还是已经使用了一段时间,你肯定会遇到一些问题,你不知道为什么你的查询没有返回你想要的结果。在这些情况下,你可以使用-v标志生成详细的输出,这可能会为你提供有关查询行为方式的信息。

高级查询

上一节介绍了基本知识,这些知识通常足以进行快速查找和筛选,但有时你可能希望使用更高级的函数和运算符,例如,在自动化涉及YAML输入和/或输出的某些任务时。那么,让我们来探索yq必须提供的更多东西。

有时,对文档中的键进行排序可能很有用,例如,如果你正在用git对YAML进行版本控制,或者只是为了一般的可读性。如果你需要区分2个YAML文件,它也非常方便。为此,我们可以使用“sortKeys(…)”函数:

# yq eval 'sortKeys(.user)' user.yaml
user:
  active: true
  addresses:
    - street: "Oxford Street"
      city: London
      country: England
    - street: "Ludwigstrasse"
      city: Munich
      country: Germany
  gender: male
  name: John
  orders:
    - 4356436
    - 4345753
    - 2345234
  surname: Smith

如果输入YAML文档是动态的,并且你不确定将出现哪些键,那么首先使用has(“键”)检查它们的存在可能是有意义的:

yq eval '.log | has("message")' log.yaml
yq eval '.log | has("code")' log.yaml
false

与has(“键”)的情况类似,在对文档进行某些操作之前,你可能需要首先获取键的动态列表,因为你可以使用keys函数:

yq eval '.log | keys' log.yaml
- level
- message

检查值的长度可能是输入筛选/验证或确保值不会溢出某些预定义边界所必需的。这是使用length函数完成的:

yq eval '.log.message | length' log.yaml
yq eval '.user.addresses[0].street | length' user.yaml

对于具有参数化输入的自动化任务,你肯定需要将环境变量传递到yq查询中。显然,你可以使用普通的shell环境变量,但最终会出现非常棘手且难以读取的引号转义。因此,最好使用yq的env函数:

level="warning" yq eval '.log.level == env(level)' log.yaml
false
level="fatal" yq eval '.log.level == env(level)' log.yaml
field="surname" yq eval '.user.[env(field)]' user.yaml
Smith

为了简化某些字段或数组的处理,你还可以使用一些字符串函数(如join或split)来连接或分解文本:

yq eval '.user.orders | join(", ")' user.yaml
4356436, 4345753, 2345234
yq eval '.log.message | split(" ")' log.yaml
- Application
- exited
- with
- code

本节最后一个也是最复杂的例子是使用reduce转换数据。要有充分的理由使用这个函数,你需要一个非常复杂的YAML文档,我不想在这里转储它。

因此,为了让你至少了解函数的工作原理,让我们使用它来实现前一个示例中join的“简单”版本:

yq eval '.user.orders[] as $item ireduce (""; (. + " ") + $item)' user.yaml
 4356436 4345753 2345234

这一条不像前面的那一条那么不言自明,所以让我们把它分解一下。查询的前半部分( .user.orders[] as $item ireduce )从YAML中获取一些字段(序列),并将其分配给变量—在本例中为 $item 。在第二部分中,我们定义了初始值 ""; (空字符串)和一个表达式,该表达式将为每个 $item 抽空-这里是以前的值,后面是空格( . + " " ,再后面是我们当前迭代的项( +$item )。

操纵和修改

大多数情况下,你只需要搜索、查找和过滤现有文档,但有时你可能还需要操作YAML并从中创建新的YAML。yq提供了几个操作符来执行这类任务,所以让我们简单地回顾一下,并看几个示例。

最简单的是联合运算符,它实际上只是一个(逗号)。它允许我们组合多个查询的结果。如果你需要同时提取YAML的多个部分,则此功能非常有用:

yq eval '.user, .log.level, .log.message' log.yaml
fatal
Application exited with code 2

另一个相当常见的用例是将记录添加到数组。这是通过+(加号)运算符完成的:

# yq eval '.user.orders += 2845234' user.yaml
  orders:
    - 4356436
    - 4345753
    - 2345234
    - 2845234

另一个方便的是update操作符(=),它更新一些字段。

# yq eval '.log.level = "warning"' log.yaml
timestamp: 1620197250
user: root
  level: warning
  message: |
    Application exited with code 2

这里需要指出的是,默认情况下,结果发送到标准输出,而不是原始文件。要进行就地更新,需要使用-i选项。

还有一些操作符可用,但不是特别有用(大多数情况下),因此我不会向你展示一些可能对你没有帮助的示例,相反,我将为你提供一些指向文档的链接,以防你想更深入地了解:

示例

现在我们了解了理论,让我们看看一些示例和命令,你可以立即将它们合并到你的工作流中。

出于显而易见的原因,我们从Kubernetes开始,因为它可能是使用YAML进行配置的最流行的项目。

yq可以帮助我们的最简单但非常有用的事情是打印Kubernetes资源或查询清单的特定部分:

kubectl get pods some-pod -o yaml | yq eval '.spec' -

我们可以做的另一件事是列出资源名称和特定属性。这对于查找或提取服务的所有侦听端口或查找命名空间中每个pod的pod映像非常方便:

kubectl get pods -o yaml | yq eval '{ .items[].metadata.name: .items[].spec.containers[0].image }' -
first-pod: quay.io/repository/image:latest
second-pod: icr.io/repository/image:latest
third-pod: icr.io/repository/image:1.0.0

请注意,上面我们必须使用.items[],因为当你获取资源的所有实例时,返回的种类是一个item列表。

对于POD、部署或服务等资源,每个命名空间中通常有许多实例,可能不希望将它们全部转储到控制台中并手动筛选。因此,你可以在某些属性上过滤它们,例如,仅列出特定端口上公开的服务的名称和侦听端口:

kubectl get svc -o yaml | yq eval '.items[] | select(.spec.ports[].port == 8443) | { .metadata.name: .spec.ports[].targetPort }' -
service-one: https
service-two: 8080

正如所有Kubernetes的“YAML工程师”所知,有时很难记住某个特定资源中的所有字段,所以可以直接查询部署的spec.template.spec的所有键。

kubectl get deploy -o yaml | yq eval '.items[0].spec.template.spec | keys' -
- containers