yq Collect into Array 操作符:用方括号把表达式结果收集成数组

发布时间:2026/9/14 17:06:20
yq Collect into Array 操作符:用方括号把表达式结果收集成数组
yq Collect into Array 操作符用方括号把表达式结果收集成数组【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq导读Collect into Array收集为数组是 yq 中最常用的数组构造操作符只要把任意表达式放进方括号[...]yq 就会把表达式求值产生的每一个结果依次收集进一个新数组。本文以 collect-into-array 官方文档 为骨架完整覆盖空数组、单元素、多元素三种基础场景并结合 operator_collect.go、operator_collect_test.go 等源码剖析其底层实现原理最后给出「先 splat、再筛选、后收集」的实战配方帮助你系统掌握 yq 中数组的构造与重组。一、什么是 Collect into Array在 yq一个可移植的命令行 YAML/JSON/XML/CSV/TOML/HCL/properties 处理器中Collect into Array操作符的核心作用只有一个使用方括号之间的表达式创建一个数组。也就是说[expr]会把expr求值得到的每一条结果都作为数组的一个元素收集起来。它不关心表达式产生的是单个标量、多个值还是一整个对象只要表达式能产出结果这些结果就会被按顺序装进一个新数组。yq 的官方定义位于 collect-into-array.md 的文档头部原文如下This creates an array using the expression between the square brackets.在 yq 的运算符体系中该操作符在 operation.go 中被注册为var collectOpType operationType{Type: COLLECT, NumArgs: 1, Precedence: 50, Handler: collectOperator}它接收 1 个参数即方括号内部的表达式优先级为 50实际处理逻辑由collectOperator函数承担。此外词法分析器在 lexer_participle.go 中也为该操作符提供了显式的collect关键字simpleOp(collect, collectOpType),这意味着除了[...]这种语法糖写法你还可以直接使用collect(...)关键字——二者等价。二、基础用法从空数组到多元素数组文档 collect-into-array.md 给出了三个递进的基础场景以下完整继承并逐一展开。1. 收集空数组Collect empty方括号内没有任何表达式时收集结果为空数组。使用--null-input标志可以不读取任何输入文件直接凭空构造文档yq --null-input []输出[]对应的测试场景定义在 operator_collect_test.go{ description: Collect empty, document: , expression: [], expected: []string{ D0, P[], (!!seq)::[]\n, }, },注意测试断言中节点的 kind 为!!seqYAML 序列类型说明空收集产出的依然是一个类型明确的序列节点而不是 null。2. 收集单个元素Collect single方括号内写一个常量或表达式得到一个只含一个元素的数组yq --null-input [cat]输出- cat对应测试operator_collect_test.go{ description: Collect single, document: , expression: [cat], expected: []string{ D0, P[], (!!seq)::- cat\n, }, },yq 输出单元素数组时会以 YAML 的块序列block sequence形式呈现即- cat。3. 收集多个元素Collect many方括号内可以使用逗号分隔多个表达式所有结果按顺序合并进同一个数组。给定一个sample.ymla: cat b: dog执行yq [.a, .b] sample.yml输出- cat - dog对应测试operator_collect_test.go{ description: Collect many, document: {a: cat, b: dog}, expression: [.a, .b], expected: []string{ D0, P[], (!!seq)::- cat\n- dog\n, }, },这里.a, .b会先后把两个路径的值推入结果流[...]再把它们整体收集为数组顺序与书写顺序完全一致。三、把任意表达式装进数组不止于逗号拼接方括号内部的表达式并不局限于a, b这种简单列表它可以是任何合法的 yq 表达式——包括管道、筛选、遍历等。这一点是 Collect into Array 最强大的地方它相当于为任意表达式结果提供了一个「重新装回数组」的出口。例如把.[]splat 展开的产物重新收集yq [.[]] sample.yml再如先在数组内部做条件筛选再收集yq [.[] | select(.type foo) | .names] sample.yml这类用法正是 recipes.md 中经典配方的核心yq [.[] | select(.type foo) | .names] | flatten | sort | unique sample.yml该配方的执行逻辑是.[]splat把数组解包让每个元素进入上下文便于逐项处理select(.type foo)过滤出符合条件的元素.names取出这些元素的names字段用[...]把上述所有中间结果重新收集成一个数组——这是整条链路的收口步骤没有它结果会散落在上下文中无法作为整体继续传递最后再交给flatten | sort | unique完成拍平、排序、去重。因此可以把 Collect into Array 理解为与 splat.[]互为逆操作.[]把数组「拆散」进入上下文流[...]把上下文流「收拢」回数组。二者配合就能在数组的元素级别做任意加工后再重组。四、底层实现原理1. 语法解析方括号如何变成 COLLECT 操作在 yq 中[...]并不是一个朴素的括号对而是词法分析器识别出的openCollect/closeCollect特殊标记。中缀表达式转后缀postfix的过程位于 expression_postfix.gocase openBracket, openCollect, openCollectObject: opStack append(opStack, currentToken) ... case closeCollect, closeCollectObject: ... result append(result, Operation{OperationType: collectOperator})当遇到]时解析器会把操作栈弹到对应的[并在结果序列中压入一个COLLECT操作expression_postfix.go。之后由 expression_parser.go 的createExpressionTree根据NumArgs把操作树组装起来——由于COLLECT的NumArgs为 1方括号内的表达式会成为它的右子节点RHS。2. 执行逻辑逐条求值并追加为子节点真正的收集逻辑在 operator_collect.go 的collectOperator函数中关键步骤为若上下文中没有任何待匹配节点MatchingNodes.Len() 0直接构造一个值为[]的空序列节点返回对应「Collect empty」场景if context.MatchingNodes.Len() 0 { node : CandidateNode{Kind: SequenceNode, Tag: !!seq, Value: []} return context.SingleChildContext(node), nil }否则创建目标序列节点Kind: SequenceNode, Tag: !!seq然后对上下文中的每一条匹配节点分别执行方括号内的 RHS 表达式d.GetMatchingNodes再把表达式产出的每一条结果通过AddChild追加为新数组的子节点operator_collect.gofor el : context.MatchingNodes.Front(); el ! nil; el el.Next() { candidate : el.Value.(*CandidateNode) collectExpResults, err : d.GetMatchingNodes(context.SingleReadonlyChildContext(candidate), expressionNode) ... for result : collectExpResults.MatchingNodes.Front(); result ! nil; result result.Next() { resultC : result.Value.(*CandidateNode) collectedNode.AddChild(resultC) } }这里有一个值得注意的细节collectOperator首先检查所有匹配节点的EvaluateTogether标志operator_collect.go。如果所有节点都允许「一起求值」通常指同一个输入文档内的节点就走collectTogether快速路径把所有结果合并进一个数组否则为每一条匹配节点分别生成独立的数组结果可能得到多个数组——这解释了为何.[] | [.name]会对数组的每个元素分别产出一个数组见 operator_collect_test.go 的测试用例。3. 测试验证文档示例即测试断言值得强调的一点是yq 的文档示例并非手写说明而是直接由测试场景生成的。在 operator_collect_test.go 中func TestCollectOperatorScenarios(t *testing.T) { for _, tt : range collectOperatorScenarios { testScenario(t, tt) } documentOperatorScenarios(t, collect-into-array, collectOperatorScenarios) }documentOperatorScenarios会把测试场景未标记skipDoc的用例渲染成 collect-into-array.md 文档。也就是说你在文档里看到的每一个示例都同时是一条经过自动化测试验证的断言命令的输出结果具备测试级别的可信度。五、进阶要点与易错提示结合源码与测试使用 Collect into Array 时有几个值得留意的点1. 空上下文返回空数组而非 null当输入文档为空如--null-input且方括号内无表达式时yq 返回[]而不是 nulloperator_collect.go这为下游处理如length、map提供了稳定的数组前提。2. 收集保留顺序.a, .b的收集顺序严格按照表达式书写顺序与匹配顺序不会做任何排序或去重若需要去重排序应像 recipes 中的配方那样在收集后显式接| sort | unique。3. 与collect(...)关键字等价由于词法分析器注册了collect关键字lexer_participle.gocollect(1)与[1]等价见 operator_collect_test.go 中collect(1)输出- 1的用例。4. 收集内部可以做赋值等副作用操作方括号内允许执行赋值表达式例如[.a cat]会先执行赋值再收集测试用例见 operator_collect_test.go。这说明[...]内部是一个完整的表达式求值上下文而非只读的取值列表。5. 与 collect-into-object 的区分yq 还有一个姊妹操作符「Create, Collect into Object」见 create-collect-into-object.md它把表达式结果收集进对象{...}而非数组[...]。需要构造 key-value 结构时请使用对象收集需要构造有序列表时使用数组收集。六、小结Collect into Array 是 yq 数组处理的核心操作符语法[expr]把expr的每一条结果按顺序收集为数组元素空表达式得到[]等价形式collect(expr)关键字与[...]等价实战价值与 splat.[]配合实现「拆散 → 加工 → 收拢」的数组重组流程是筛选、去重、排序等组合配方的收口环节实现层面语法上由 expression_postfix.go 将[...]转为COLLECT操作运行时由 operator_collect.go 的collectOperator/collectTogether执行逐条求值与追加可信度所有文档示例均来自 operator_collect_test.go 的自动化测试断言可放心照抄使用。如果你正在处理「过滤数组后重新组合」「把散落结果重新收拢为列表」之类的需求[expr]就是那个最直接的答案。【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考