尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

yq 的 Add 操作符(+ / +=):数组拼接、数字加法、字符串连接与 Map 浅合并全指南

yq 的 Add 操作符(+ / +=):数组拼接、数字加法、字符串连接与 Map 浅合并全指南 yq 的 Add 操作符 / 数组拼接、数字加法、字符串连接与 Map 浅合并全指南【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq本文基于 yq 官方操作符文档 add.md 编写并结合作者仓库中 operator_add.go、operators.go 等源码实现与 operator_add_test.go 测试用例进行纵深印证。读完本文你将掌握 yq 中与的完整语义——包括数组拼接、数值递增、字符串拼接、日期加时长、Map 浅合并以及自定义标签Custom Tag类型的处理并能独立判断何时用、何时用、何时改用*深度合并。Add 操作符的类型分派规则add操作符对应的行为取决于左侧操作数LHS的类型这一点是理解一切后续示例的基石。文档 add.md 给出了四条分派规则arrays序列拼接concatenatenumber scalars数字标量算术加法string scalars字符串标量字符串拼接maps映射浅合并shallow merge若要深度合并请使用 multiply 操作符*。同时文档强调使用作为“相对追加赋值”relative append assign适合自增等场景。.a .x完全等价于.a .a .x即先把 LHS 取出来与 RHS 相加再写回 LHS 指向的位置。从源码看这一分派逻辑位于 operator_add.go 的add()函数它先处理lhs nil、rhs nil以及 LHS 为!!null的边界情况然后按lhsNode.Kind分别进入MappingNode → addMaps、SequenceNode → addSequences、ScalarNode → addScalars三条分支。若 RHS 类型与 LHS 不匹配例如把序列加到映射上会直接返回形如!!seq () cannot be added to a !!map (a)的错误——这一点在 operator_add_test.go 中有对应的expectedError测试用例佐证。的实现则更值得关注在 operators.go 的compoundAssignFunction中yq 会遍历 LHS 匹配到的每个节点对其做一次拷贝然后构造一个等价于LHS LHS RHS的赋值操作节点再求值operator_add.go 中的addAssignOperator正是把“计算函数”换成createAddOp后交给该通用函数执行。数组拼接Concatenate arrays当 LHS 是数组时将两个数组首尾相接。给定sample.ymla: - 1 - 2 b: - 3 - 4执行yq .a .b sample.yml输出- 1 - 2 - 3 - 4源码上addSequencesoperator_add.go先保留 LHS 的 Tag 与样式再把lhs.Content与 RHS 转换出的节点依次AddChildren拼接。其中 RHS 节点通过toNodesoperator_add.go转换若 RHS 是!!null则视为空序列不产生任何元素若 RHS 本身是序列则取其子节点其他类型的标量/映射则包装成单个节点。yq .a .b这种纯读取式求值不会修改原文件对应测试见 operator_add_test.go。拼接进已有数组Concatenate to existing array用把数组追加到已有数组上注意a原有的样式styling会被保留。给定sample.ymla: [1,2] b: - 3 - 4执行yq .a .b sample.yml输出a仍是紧凑的流式风格[1, 2, 3, 4]a: [1, 2, 3, 4] b: - 3 - 4这里的“保留样式”在源码中有明确体现toNodes在把非序列的 RHS 包装成节点时会借用 LHS 第一个子节点的Styleoperator_add.go从而让追加的元素与已有元素风格一致对应测试用例见 operator_add_test.go。向数组拼接 nullConcatenate null to array给定sample.ymla: - 1 - 2执行yq .a null sample.yml输出null被忽略数组保持不变- 1 - 2这正是toNodes中candidate.Tag !!null时返回空节点列表的实现效果operator_add.go测试见 operator_add_test.go。向已有数组追加元素Append to existing arrayRHS 是单个标量时把它作为一个元素追加到数组末尾且样式从已有数组元素复制。给定sample.ymla: [dog]执行yq .a cat sample.yml输出a: [dog, cat]注意输出中新增的cat同样带单引号这正是“样式复制”的直接证据标量节点被包装时套用了 LHS 首个元素dog的引号风格。测试见 operator_add_test.go。向已有数组前置元素Prepend to existing array不可交换LHS 类型决定语义所以前置要显式把新元素写在左边给定sample.ymla: - dog执行yq .a [cat] .a sample.yml输出a: - cat - dog这里[cat]作为 LHS 数组在前.a作为 RHS 在后拼接结果自然是cat在前。测试见 operator_add_test.go。向数组追加对象Add new object to array当 RHS 是映射对象时它作为单个元素追加进数组给定sample.ymla: - dog: woof执行yq .a {cat: meow} sample.yml输出- dog: woof - cat: meow相对追加Relative append的“相对”特性配合通配遍历如.a[]非常强大可以对匹配到的每一个位置分别执行追加。给定sample.ymla: a1: b: - cat a2: b: - dog a3: {}执行yq .a[].b [mouse] sample.yml输出a1.b、a2.b各自追加a3原本没有byq 会为它自动创建b: [mouse]a: a1: b: - cat - mouse a2: b: - dog - mouse a3: b: - mouse注意a3: {}变成a3: {b: [mouse]}——这是“相对赋值 缺失路径自动补全”的典型效果测试见 operator_add_test.go。之所以能自动补全缺失的b键是因为展开后的赋值操作走的是 operator_assign.go 中assignUpdateOperator的UpdateAssign分支该分支会基于 LHS 匹配结果直接更新对应候选节点。字符串拼接String concatenationLHS 是字符串时直接做文本拼接给定sample.ymla: cat b: meow执行yq .a .b sample.yml输出a: catmeow b: meow源码addScalarsoperator_add.go中当lhsTag !!str时执行target.Value lhs.Value rhs.Value并保留 LHS 的 Tag。更细节的是若 RHS 是!!null结果保持 LHS 原值等价于字符串加空若 RHS 是字符串而 LHS 不是则结果 Tag 以 RHS 为准。测试见 operator_add_test.go。数字加法浮点Number addition - float只要 LHS 或 RHS 之一是浮点数整个计算就按浮点进行给定sample.ymla: 3 b: 4.9执行yq .a .a .b sample.yml输出a: 7.9 b: 4.9源码上operator_add.go 的浮点分支通过strconv.ParseFloat解析两边后相加并把结果 Tag 标为!!float。测试见 operator_add_test.go。数字加法整数Number addition - int当 LHS 与 RHS都是整数时才按整数运算给定sample.ymla: 3 b: 4执行yq .a .a .b sample.yml输出a: 7 b: 4源码中整数分支operator_add.go通过parseInt64解析并且会保留 LHS 数值的原始格式format——例如十六进制或前导零等书写风格会随结果保留。测试见 operator_add_test.go。数字递增Increment numbers利用 1可以对任意匹配到的数值原地自增给定sample.ymla: 3 b: 5执行yq .[] 1 sample.yml输出a: 4 b: 6这是作为“相对追加赋值”最直接的应用把.a 1理解为“取.a的值加 1 再写回.a”。测试见 operator_add_test.go。日期加法Date additionyq 支持给日期加上时长duration。默认假设输入为RFC3339日期时间格式。给定sample.ymla: 2021-01-01T00:00:00Z执行yq .a 3h10m sample.yml输出a: 2021-01-01T03:10:00Z时长字符串如3h10m使用 Go 标准库的time.ParseDuration解析源码 operator_add.go 的addDateTimes完成“解析时长 → 解析当前时间 → 相加 → 按原格式回写”的完整链路。判断是否走日期分支的依据是LHS 的 Tag 为!!timestamp或者 LHS 是字符串且能被当前日期时间格式解析operator_add.go。测试见 operator_add_test.go。关于日期时间的更多操作格式化、时区、Unix 时间戳互转等可参考仓库中的 datetime 操作符文档。日期加法自定义格式Date addition - custom format当日期不是 RFC3339 格式时需要用with_dtf指定解析格式格式语法基于 Go 的time库给定sample.ymla: Saturday, 15-Dec-01 at 2:59AM GMT执行yq with_dtf(Monday, 02-Jan-06 at 3:04PM MST, .a 3h1m) sample.yml输出a: Saturday, 15-Dec-01 at 6:00AM GMTwith_dtf的第一个参数设置日期时间解析格式第二个参数是要在该格式上下文中执行的任意 yq 表达式格式字符串中的Monday、02-Jan-06、3:04PM、MST分别对应 Go 参考时间Mon Jan 2 15:04:05 MST 2006的各个组成部分。测试见 operator_add_test.go更完整的格式与用法说明见 datetime.md。与 null 相加Add to null“加 null 就是返回 RHS”当 LHS 为null时直接返回 RHS 本身。运行yq --null-input null cat输出cat注意这里用了--null-input标志表示不读取输入文件、直接以null作为初始上下文。对应源码是 operator_add.go 中lhsNode.Tag !!null时执行lhs.CopyAsReplacement(rhs)测试见 operator_add_test.go。这个特性也解释了为何“给缺失字段做运算”不会报错——缺失字段按null处理。Map 的浅合并Add maps to shallow merge当 LHS 是映射Map时执行浅合并shallow merge逐 key 处理新 key 直接追加已有 key 用 RHS 的值整体替换不会递归合并子映射。若需要递归深度合并请使用*操作符见 multiply-merge 文档。给定sample.ymla: thing: name: Astuff value: x a1: cool b: thing: name: Bstuff legs: 3 b1: neat执行yq .a .b sample.yml输出a: thing: name: Bstuff legs: 3 a1: cool b1: neat b: thing: name: Bstuff legs: 3 b1: neat观察关键点a.thing整个被b.thing替换所以value: x消失了、也没有legs: 3与name: Astuff并存的“合并”效果——这是浅合并与深合并的本质区别a1: cool保留LHS 独有 keyb1: neat被追加进来RHS 独有 key。源码addMapsoperator_add.go正是按此逻辑实现先把 LHS 的所有键值对加入结果再遍历 RHS 的键值对通过findKeyInMap查找 key——找不到则AddKeyValueChild追加找到了则用CopyAsReplacement整体替换该 key 的值。测试见 operator_add_test.go。自定义标签类型本质是字符串Custom types that are really stringsYAML 允许通过!tag给节点打上自定义标签。当遇到自定义标签时yq 会尝试解码其底层类型再决定运算方式。给定sample.ymla: !horse cat b: !goat _meow执行yq .a .b sample.yml输出按字符串拼接且 LHS 的自定义标签!horse被保留a: !horse cat_meow b: !goat _meow源码中addScalars对 LHS 先判断其 Tag 是否以!!开头不是即自定义标签则调用guessTagFromCustomType()猜出底层类型operator_add.go计算完成后结果 Tag 仍沿用 LHS 的自定义标签。测试见 operator_add_test.go。自定义标签类型本质是数字Custom types that are really numbers同样的“解码底层类型”规则也适用于数字给定sample.ymla: !horse 1.2 b: !goat 2.3执行yq .a .b sample.yml输出a: !horse 3.5 b: !goat 2.31.2 2.3按浮点计算出3.5结果保留!horse标签b保持原样。测试见 operator_add_test.go。从测试用例还能看到更多扩展情形自定义 int 相加保留!horse 5operator_add_test.go、自定义数组!horse [a] !goat [b]得到!horse [a, b]operator_add_test.go以及含锚点数组horse [1] 2追加后锚点仍保留operator_add_test.go——这些都印证了“先解码底层类型、再按类型分派、结果保留 LHS 自定义标签”的统一原则。常见类型不匹配错误在阅读 operator_add_test.go 时还能看到两类典型报错场景可作为排错参考把序列加到映射.a [2]当a是映射时报!!seq () cannot be added to a !!map (a)把序列加到标量.a [2]当a是字符串时报!!seq () cannot be added to a !!str (a)。这两条错误均来自 operator_add.go 中对MappingNode/ScalarNode分支的 RHS 类型校验测试见 operator_add_test.go。小结如何选择、与*场景推荐写法语义纯计算、不改文件yq .a .b f.yml按 LHS 类型分派输出结果追加/自增/原地修改.a .b等价于.a .a .b写回原位置深度合并映射.a * .b递归合并见 multiply-merge.md给日期加时长.a 3h10m默认 RFC3339自定义格式用with_dtf向数组前置元素.a [cat] .a顺序敏感新元素放左边一句话总结是一个按 LHS 类型分派的多态操作符是其“取当前值计算后写回”的语法糖。理解 operator_add.go 中add、addScalars、addSequences、addMaps、addDateTimes五条核心路径的分工就能准确预判任何表达式的输出再结合 operator_add_test.go 中覆盖上述全部场景的TestAddOperatorScenarios测试用例可以放心地把这些表达式应用到自己的 YAML/JSON 处理流水线中。【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表