操作符 `..` 与 `...` 完全指南)
yq 递归下降Recursive Descent / Glob操作符..与...完全指南【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq本文以 yq 官方操作符文档 recursive-descent-glob.md 为核心骨架结合 operator_recursive_descent.go 等源码实现与测试用例系统讲解 yq 的递归下降操作符..仅匹配值节点与...同时匹配映射键节点的语法、行为差异、典型实战场景递归查找、批量改样式、锚点与合并键的遍历边界并给出大量可直接复制的命令行示例与输入输出对照帮助你在 YAML/JSON/XML/TOML 等多格式文档中高效地一把抓取所有嵌套节点。yqportable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor中的递归下降操作符Recursive Descent文档中亦称 Glob可以在不提前知道文档结构的前提下递归地匹配某个元素下的所有子节点包括该元素自身是编写无论嵌套多深都能生效的通用过滤、批量修改表达式的最重要武器。它直接对标jq的..并针对 YAML 特性做了扩展本文会从用法、输出顺序、实现原理到边界行为逐一讲透。一、操作符概览..与...两种形式根据官方文档定义该操作符递归匹配或 glob给定元素的全部子节点包括该节点自身。最常用于对全部匹配结果递归应用一个过滤器。yq 提供两种写法形式含义典型用途..仅递归匹配所有值value节点不含映射键map key查找/修改特定值...递归匹配所有值节点 映射键节点YAML 中需要对键本身做样式、标签tag、锚点/别名处理时语法层面的实现依据两种形式在词法分析阶段就被区分开来见 lexer_participle.go{RecursiveDecentIncludingKeys, \.\.\., recursiveDecentOpToken(true), 0}, {RecursiveDecent, \.\., recursiveDecentOpToken(false), 0},recursiveDecentOpToken(includeMapKeys bool)会根据是否包含键来构造不同的遍历偏好lexer_participle.gofunc recursiveDecentOpToken(includeMapKeys bool) yqAction { prefs : recursiveDescentPreferences{ RecurseArray: true, TraversePreferences: traversePreferences{ DontFollowAlias: true, // 不跟随别名锚点引用 IncludeMapKeys: includeMapKeys, // .. 为 false... 为 true }, } return opTokenWithPrefs(recursiveDescentOpType, nil, prefs) }可以看到..与...对应的是同一个操作符类型RECURSIVE_DESCENT定义见 operation.go优先级为 50无参数二者的差异完全由IncludeMapKeys这一个偏好开关决定两者都设置了DontFollowAlias: true这正是文档中别名不会被遍历Aliases are not traversed一节行为背后的源码依据RecurseArray: true表示数组序列中的每个元素也会被递归展开。操作符的执行入口核心实现在 operator_recursive_descent.gofunc recursiveDescentOperator(_ *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { var results list.New() preferences : expressionNode.Operation.Preferences.(recursiveDescentPreferences) err : recursiveDecent(results, context, preferences) ... return context.ChildContext(results), nil } func recursiveDecent(results *list.List, context Context, preferences recursiveDescentPreferences) error { for el : context.MatchingNodes.Front(); el ! nil; el el.Next() { candidate : el.Value.(*CandidateNode) results.PushBack(candidate) // 先输出当前节点自身 if candidate.Kind ! AliasNode len(candidate.Content) 0 (preferences.RecurseArray || candidate.Kind ! SequenceNode) { children, err : splat(context.SingleChildContext(candidate), preferences.TraversePreferences) ... err recursiveDecent(results, children, preferences) // 深度优先递归 ... } } return nil }从实现可以看出三个关键点深度优先DFS遍历先输出节点自身再通过splat展开直接子节点然后对每个子节点递归调用自身先父后子的输出顺序因此yq ..的输出中父节点总是出现在它的子节点之前别名节点AliasNode不会继续展开candidate.Kind ! AliasNode条件保证了遍历到*cat这类别名节点时就停止不会沿着别名指向的锚点继续递归详见下文第六节。二、只匹配值的..设置所有值节点的样式用法说明yq .. style flow file.yaml这条命令会把 YAML 文档中所有值节点的样式设置为 flow流式风格但不会修改映射键的样式。这是文档给出的第一个例子也是最常用的批量修改场景无论文档嵌套多深、是 map 还是数组..都能覆盖到。关于style操作符的更多细节可参见同目录下的 style 操作符文档。为什么需要区分值和键在 YAML 中与 JSON 不同映射键map key本身也是一个节点拥有自己的样式、标签tag甚至可以是锚点/别名的目标。例如a: frog这里的a是一个键节点frog是一个值节点。用..只能命中frog而键a的样式/标签需要通过...才能操作。三、同时匹配值 键的...连键一起处理用法说明yq ... style flow file.yaml与..不同...会把映射键也纳入结果集。文档特别指出这在 YAML 中尤其有用因为与 JSON 不同YAML 的映射键可以有独立的样式、标签也可以使用锚点anchors和别名aliases。例如你想统一某个文档所有节点的引号风格、flow/block 风格就必须用...才能把键节点也覆盖到。从实现上看...只是把IncludeMapKeys置为true在遍历 map 时键和值都会作为候选节点被压入结果列表见 operator_traverse_path.go 中doTraverseMap对IncludeMapKeys的处理。四、完整实战示例含输入输出对照下面的示例均来自官方文档并补充了测试依据见 operator_recursive_descent_test.go 中同名场景每个场景都给出输入、命令、输出三段方便直接验证。4.1 递归 map仅值形式..输入sample.ymla: frog命令yq .. sample.yml输出a: frog frog分析..先输出根节点整个 mapa: frog再输出其值节点frog。测试用例对应 operator_recursive_descent_test.goD0, P[], (!!map)::{a: frog}与D0, P[a], (!!str)::frog。4.2 递归查找带指定键的节点输入sample.ymla: name: frog b: name: blog age: 12命令[]只是为了把多个匹配结果收集成数组展示路径表达式中不包[]也可以yq [.. | select(has(name))] sample.yml输出- name: frog b: name: blog age: 12 - name: blog age: 12分析..递归产生所有节点select(has(name))过滤出拥有name键的节点命中两个顶层的a节点包含name: frog与嵌套的b和内部的b节点name: blog, age: 12。这是一个典型的递归找对象模式不需要知道嵌套深度直接筛出所有满足条件的节点。测试场景见 operator_recursive_descent_test.go。4.3 递归查找指定值的节点输入sample.ymla: nameA: frog b: nameB: frog age: 12命令yq .. | select(. frog) sample.yml输出frog frog分析递归遍历所有值节点只保留值等于字符串frog的节点两处frog都被找到。测试场景见 operator_recursive_descent_test.go对应的节点路径为P[a nameA]与P[a b nameB]。实战提示把select(. frog)换成select(. | type ~~number)之类的类型判断或配合 select 操作符文档、filter 操作符文档 中的条件就可以实现递归找出所有数字/所有空值/所有匹配正则的节点等能力。4.4 递归 map值 键形式...输入sample.ymla: frog命令yq ... sample.yml输出a: frog a frog分析注意与 4.1 的差异——多输出了一个a这正是映射键节点。测试用例见 operator_recursive_descent_test.go输出顺序为根 map → 键aP[a], (!!str)::a→ 值frog。对于嵌套结构...的展开顺序深度优先、先父后子可参考测试中的完整断言operator_recursive_descent_test.go{a: {b: apple}}会被展开为根节点 →a键 →{b: apple}子 map →b键 →apple值共 5 个节点。五、数组序列的递归展开虽然官方文档正文未单独举例但源码与测试明确了数组在递归下降中的行为RecurseArray: true意味着数组中的每个元素都会被展开同时..与...对数组而言行为一致数组没有键节点概念。以输入[1,2,3]为例测试见 operator_recursive_descent_test.goyq .. sample.yml输出...结果完全相同- 1 - 2 - 3 1 2 3对于混合结构[{a: cat}, 2, true]..会依次输出根序列 → 第一个 map 元素 → 其键a的值cat→2→true见 operator_recursive_descent_test.go。这也验证了递归下降操作符对 JSON/XML 等以数组为主要容器格式同样有效。六、边界行为一别名Alias不会被遍历输入sample.ymla: cat c: frog b: *cat命令yq [..] sample.yml输出- a: cat c: frog b: *cat - cat c: frog - frog - *cat分析根 map 被输出cat锚点节点本身含c: frog被输出并继续展开出frog但b: *cat中的别名引用*cat只是被作为节点输出显示为*cat而不会再沿着别名指向的锚点去递归展开它的内容即不会重复输出c: frog。这正是recursiveDecent中candidate.Kind ! AliasNode判断的直接体现operator_recursive_descent.go遍历到别名节点时只把它本身放进结果集停止继续下钻。测试场景见 operator_recursive_descent_test.go。七、边界行为二合并文档Merge Docs /合并键不会被遍历输入sample.ymlfoo: foo a: foo_a thing: foo_thing c: foo_c bar: bar b: bar_b thing: bar_thing c: bar_c foobarList: b: foobarList_b : - *foo - *bar c: foobarList_c foobar: c: foobar_c : *foo thing: foobar_thing命令yq .foobar | [..] sample.yml输出- c: foobar_c : *foo thing: foobar_thing - foobar_c - *foo - foobar_thing分析对foobar节点递归展开时: *foo这个合并键的值*foo别名只是被当作普通节点输出*foo并不会沿着合并键展开foo锚点中的a、thing、c等内容。也就是说递归下降不会穿透合并merge锚点。从实现角度看这一行为有两层保障遍历偏好中的DontFollowAlias: true使别名不被跟随见第一节源码recursiveDecent对AliasNode的短路判断operator_recursive_descent.go。测试用例见 operator_recursive_descent_test.go...形式含键下的展开结果见同文件 L194-L200其中合并键会作为键节点tag 为!!merge出现在结果里。八、源码视角操作符优先级与组合使用RECURSIVE_DESCENT操作符在 operation.go 中定义的优先级为 50且不带参数var recursiveDescentOpType operationType{Type: RECURSIVE_DESCENT, NumArgs: 0, Precedence: 50, Handler: recursiveDescentOperator}这带来两个实用推论可以与管道|、过滤select自由组合如.. | select(...)、.. | has(x)、... | . style flow等写法解析时递归下降操作符会作为管道左侧的节点源可以作为赋值update表达式的左值第一节的两个样式设置示例.. style flow与... style flow正是递归下降 赋值的经典组合——左侧../...产出所有目标节点右侧style对每个节点做批量修改。若想进一步了解遍历路径与赋值操作符可参考 traverse-read 操作符文档 与 assign-update 操作符文档。九、常见问题速查问题答案..和...有什么区别..只匹配值节点...额外匹配映射键节点YAML 特有需求。递归输出顺序是什么深度优先、先父后子父节点总是先于其子孙输出由 operator_recursive_descent.go 的实现顺序保证。会不会递归进锚点/别名不会。别名节点会被输出但不会被展开合并键同样不会被穿透。数组会被递归吗会。RecurseArray: true数组每个元素都会展开且数组场景下..与...行为一致。空文档/空 map/空数组会怎样测试表明仅输出根节点自身见 operator_recursive_descent_test.go 中{}、[]、cat三个场景。从哪里查看全部测试场景operator_recursive_descent_test.go 中的recursiveDescentOperatorScenarios及TestRecursiveDescentOperatorScenarios覆盖本文所有示例及更多边界输入。十、总结递归下降Recursive Descent / Glob操作符是 yq 表达式体系中面向未知结构的通用遍历能力..递归匹配全部值节点适合批量查找/修改值...在..基础上把映射键也纳入匹配集适合 YAML 中需要处理键的样式、标签、锚点/别名的场景二者共享同一份核心实现recursiveDescentOperator差异仅由IncludeMapKeys偏好开关控制且都默认不跟随别名遍历采用深度优先、先父后子顺序数组元素会被展开但锚点别名与合并文档不会被穿透。掌握..与...再配合select、has、style、赋值等操作符即可写出无论文档多深、结构多复杂都能一击命中的通用 yq 表达式。延伸阅读仓库内相关文档操作符文档主页Main.mdfilter.md在递归结果上做过滤的常用搭档select.md配合.. | select(...)实现条件筛选style.md本文示例中style赋值操作符详解anchor-and-alias-operators.md理解锚点/别名节点行为的基础核心实现operator_recursive_descent.go、词法定义 lexer_participle.go、操作符注册 operation.go【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考