news 2026/9/14 17:54:19

yq Pipe 管道操作符:把一个表达式接进下一个表达式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
yq Pipe 管道操作符:把一个表达式接进下一个表达式

yq Pipe 管道操作符:把一个表达式接进下一个表达式

【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq

yq 是一款可移植的 YAML、JSON、XML、CSV、TOML、HCL 与 properties 文件处理器,它的查询语言中最核心的“粘合剂”就是管道(Pipe)操作符|。本篇基于仓库文档 pipe.md 展开:先完整覆盖文档中的简单管道与连续更新两个用法,再结合 yq 的词法解析、操作符注册与求值器源码,讲清楚|的优先级、求值时机与上下文传递机制,帮助你写出可预测、可组合的 yq 表达式。

什么是 Pipe:把表达式的结果喂给下一个表达式

Pipe 的行为与 Bash 中的|类似:左边表达式的匹配结果,成为右边表达式的输入上下文。在 yq 中,这个“结果”是一组候选节点(candidate nodes)——既可能是值本身,也可能是带有路径信息的节点(这样赋值操作才能写回原文档)。

官方文档对 Pipe 的一句话定义是:“Pipe the results of an expression into another. Like the bash operator.”(把一个表达式的结果传给另一个表达式,就像 Bash 操作符一样)。

简单管道:逐级深入取值

文档给出的第一个例子:给定一个sample.yml文件:

a: b: cat

执行:

yq '.a | .b' sample.yml

输出:

cat

执行过程可以拆解为两步:

  1. .a在文档上求值,得到节点b: cat这个映射子树(路径a);
  2. 管道把该子树作为新的输入上下文,.b在子树上求值,得到标量cat

也就是说,.a | .b等价于写.a.b,但拆成两段管道后,你可以在中间插入任意变换操作,这正是管道表达力的来源。例如在包含数组的数据上:

yq '.a[] | select(.b == "cat")' sample.yml # 先展开数组,再过滤

管道两侧的表达式各自独立求值,右侧表达式的起点是左侧的终点,这使 yq 表达式天然适合链式组合(selectmapsort等单目/遍历操作符都常与|搭配使用,相关文档见 select.md、map.md)。

连续更新:管道串起多个赋值

文档的第二个例子展示了管道在“写”方向的价值。给定sample.yml

a: cow b: sheep c: same

执行:

yq '.a = "cat" | .b = "dog"' sample.yml

输出:

a: cat b: dog c: same

三个要点:

  • 从左到右依次执行.a = "cat"先完成,文档状态更新后,.b = "dog"在此基础上执行,最后c: same未被触及而原样保留;
  • 每条更新都作用于同一份文档:与“读取结果再传递”不同,赋值类操作直接修改文档树,管道只是规定了操作的先后顺序;
  • 更新表达式可以很长:把一次复杂的文档变换拆成多个以|分隔的小步骤,是 yq 表达式可读性的关键实践。

仓库中的测试用例 operator_pipe_test.go 精确复现了文档这两个例子:

{ description: "Simple Pipe", document: `{a: {b: cat}}`, expression: `.a | .b`, expected: []string{ "D0, P[a b], (!!str)::cat\n", }, }, { description: "Multiple updates", document: `{a: cow, b: sheep, c: same}`, expression: `.a = "cat" | .b = "dog"`, expected: []string{ "D0, P[], (!!map)::{a: cat, b: dog, c: same}\n", }, },

测试中的P[a b]表示最终节点路径为a.b,说明管道确实逐级推进了路径;P[]表示第二个例子的结果是文档根节点本身(更新后输出整份文档)。

变量与管道:as $x |的专门入口

yq 的变量赋值(expr as $x | ...)在求值器层面是走管道分支的。pipe.md 未展开这一点,但源码 operator_pipe.go 中有明确的专门处理:

func pipeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { if expressionNode.LHS.Operation.OperationType == assignVariableOpType { return variableLoop(d, context, expressionNode) } lhs, err := d.GetMatchingNodes(context, expressionNode.LHS) ... rhsContext := context.ChildContext(lhs.MatchingNodes) rhs, err := d.GetMatchingNodes(rhsContext, expressionNode.RHS) ... return context.ChildContext(rhs.MatchingNodes), nil }

当管道左侧是变量赋值(as)时,会转交给 variableLoop 处理;该文件中的注释直接说明设计意图——“variables are like loops in jq”(变量像 jq 中的循环一样,逐值展开执行右侧)。而普通管道则严格按两阶段执行:

  1. GetMatchingNodes(context, LHS)在当前上下文上求值左表达式;
  2. context.ChildContext(lhs.MatchingNodes)用左侧匹配结果构造子上下文;
  3. 在子上下文上求值右表达式,其结果成为管道整体输出。

变量操作的约束同样写在代码里:useWithPipe会在未配合管道使用时报错must use with a pipe, e.g. exp as $x | ...,且 RHS 必须是变量名(见 variableLoopSingleChild)。

优先级:管道在表达式中处于哪一层

|的词法识别在 lexer_participle.go 中注册({"Pipe", "\|", opToken(pipeOpType), 0}),其操作符定义在 operation.go:

var pipeOpType = &operationType{Type: "PIPE", NumArgs: 2, Precedence: 30, Handler: pipeOperator}

对照 operation.go 中全部操作符的优先级(数字越大结合越紧),管道处于一个关键的分界位置,从低到高大致为:

优先级操作符说明
10union、块a // b(并集)、括号块
15CREATE_MAP对象字面量{...}的构建
20OR/AND布尔运算or/and
30PIPE普通管道\|
35REDUCE归约
40ASSIGN/EQUALS赋值=、比较==
42MULTIPLYADDALTERNATIVE*+、备选//相关运算
45SHORT_PIPE集合/对象字面量内部的短管道(如[... | ...]
50+LENGTHSORTCOLLECT无参/单参数后缀操作

由此得到三条实用的求值规则:

  • 赋值高于管道.a = "cat" | .b = "dog"被解析为(赋值) | (赋值),而不是.a = ("cat" | .b...)。这就是“连续更新”能成立的原因;
  • 布尔运算低于管道x and y | zand一侧的表达式会先被管道切开,需要用括号显式控制结合关系;
  • 算术/备选优先于管道"a" + "b" | upper会先完成拼接再进管道。

此外源码中还定义了 shortPipeOpType(优先级 45),它与pipeOpType共用同一个pipeOperator处理器,服务于[... | ...]这类集合内部语法;普通表达式中书写|时对应的是上表的PIPE

上下文的传递与“只读”保护

理解管道的第二个关键点在于上下文如何跨|传递。每个求值步都携带一个Context(见 context.go),核心字段包括:

type Context struct { MatchingNodes *list.List // 当前匹配到的候选节点 Variables map[string]*list.List // 管道间共享的变量 DontAutoCreate bool // 只读标志:禁止自动创建路径 datetimeLayout string }

ChildContext在克隆上下文时复制变量表但保留候选节点引用,源码注释解释了原因:不复制节点才能让ref(引用)操作符正常工作——即左侧表达式如果返回的是文档中真实存在的节点(而非值拷贝),右侧的修改会写回原树。这正是.a | .b = "x"能真正改到a.b的机制。

测试文件中的第三个用例专门验证了“管道不把只读状态泄漏给右侧”:

{ description: "Don't pass readonly context", expression: `(3 + 4) | ({} | .b = "dog")`, expected: []string{ "D0, P[], (!!map)::b: dog\n", }, },

左侧(3 + 4)产生的是纯数值结果(对映射求值属于只读语义),但管道右侧{}开启的新对象字面量是全新的可写节点,因此.b = "dog"正常执行并输出b: dog。换句话说:只读保护作用于“读取既有节点”的路径,而不会阻塞在管道右侧构造的新节点上的写入

小结

回到 pipe.md 的核心内容:

  • 读取场景.a | .b逐级深入,每段表达式都以上一段的输出为起点;
  • 更新场景.a = "cat" | .b = "dog"从左到右依次修改同一份文档,未涉及的键保持不变;
  • 实现层面PIPE操作符(优先级 30,处理器 pipeOperator)两阶段求值——先算左侧、用ChildContext传递匹配节点、再算右侧;变量as走同一入口但由 variableLoop 逐值展开。

掌握这三层,你就能在任何 yq 表达式中正确预测|的结合与执行顺序,把复杂的查询与批处理改写为清晰、可维护的管道链。

【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 17:52:49

闲鱼90元戴尔准系统改造NAS:1800元配置单与实战

上个月我蹲闲鱼的时候又刷到一台戴尔OptiPlex 7010 USFF,卖家标价90块,裸机,不带电源适配器,侧面还缺一颗螺丝。我犹豫了大概十秒钟,拍了。这种老准系统改NAS的玩法,是我这两年折腾下来觉得最划算的方案。整…

作者头像 李华
网站建设 2026/9/14 17:52:39

CentOS 7.9停服后如何换阿里源?yum仓库配置与排错全指南

1. 不换源真不行:CentOS 7.9官方仓库停服后的连锁反应接手一台CentOS 7.9服务器,我建议你第一件事先看yum源。很多人新装完系统,习惯性敲一句yum install -y vim,等了好几分钟才发现要么卡在"Loading mirror speeds from cac…

作者头像 李华
网站建设 2026/9/14 17:51:23

Flutter与OpenHarmony开发视力保护App的反馈系统实现

1. 项目背景与核心需求视力保护提醒App在数字时代显得尤为重要。随着人们每天面对电子屏幕的时间越来越长,眼睛疲劳、干涩等问题日益普遍。这款基于Flutter和OpenHarmony开发的App,核心功能是通过定时提醒帮助用户养成科学用眼的习惯。意见反馈系统作为A…

作者头像 李华
网站建设 2026/9/14 17:51:01

DAB变换器控制策略:PI与MPC对比分析

1. 双有源全桥DAB变换器控制策略概述双有源全桥(Dual Active Bridge, DAB)变换器作为现代电力电子系统中的关键部件,在中大功率DC/DC转换领域占据重要地位。这种拓扑结构因其独特的双向能量传输能力和高频电气隔离特性,被广泛应用…

作者头像 李华