yq 比较操作符完全指南数字、字符串与日期时间的大小比较实战【免费下载链接】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 数据处理中编写精确筛选条件的实战能力。比较操作符概览yq 的比较操作符Compare Operators包含大于、大于等于、小于、小于等于四个二元操作符用于比较相同类型的标量值scalar values比较结果是一个布尔值节点。目前支持比较的标量类型如下数字numbers包括整数与浮点数字符串strings按字节码bytecode顺序比较日期时间datetimes默认按 RFC3339 格式解析比较在官方文档的定义之外这四个操作符常与select、/!、and/or等配合构成完整的筛选与过滤表达式。底层原理比较操作符如何被解析与执行要真正用好比较操作符有必要理解它在 yq 内部的实现路径。整个流程分为「词法解析 → 操作注册 → 跨节点求值 → 标量比较」四个环节。词法解析四个操作符的 token 定义在词法器中四个比较操作符被定义为独立的 token并且每个 token 都携带了compareTypePref偏好结构{GreaterThanEquals, \s*\s*, opTokenWithPrefs(compareOpType, nil, compareTypePref{OrEqual: true, Greater: true}), 0}, {LessThanEquals, \s*\s*, opTokenWithPrefs(compareOpType, nil, compareTypePref{OrEqual: true, Greater: false}), 0}, {GreaterThan, \s*\s*, opTokenWithPrefs(compareOpType, nil, compareTypePref{OrEqual: false, Greater: true}), 0}, {LessThan, \s*\s*, opTokenWithPrefs(compareOpType, nil, compareTypePref{OrEqual: false, Greater: false}), 0},compareTypePref结构定义于operator_compare.go只有两个布尔字段OrEqual是否包含相等情形与为true与为falseGreater方向是「大于」还是「小于」这一设计将四个操作符统一抽象为「方向 是否含等」两个维度为后续统一的比较逻辑打下基础。操作注册COMPARE 类型在operation.go中比较操作符注册为二元操作NumArgs: 2优先级 40并绑定处理器compareOperatorvar compareOpType operationType{Type: COMPARE, NumArgs: 2, Precedence: 40, Handler: compareOperator}同时min最小值与max最大值操作符也基于同一套比较机制实现这一点我们会在后文展开。跨节点求值crossFunctioncompareOperator的入口实现位于operator_compare.gofunc compareOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { prefs : expressionNode.Operation.Preferences.(compareTypePref) return crossFunction(d, context, expressionNode, compare(prefs), true) }它从表达式节点中取出词法阶段注入的compareTypePref然后通过crossFunction对左右两侧LHS/RHS候选节点进行配对求值真正的比较逻辑封装在compare(prefs)返回的闭包函数中。标量比较compareScalars 的分支逻辑闭包函数在operator_compare.go中先做空值与容器类型检查随后调用compareScalars见operator_compare.go按标签tag分派到具体比较分支先通过guessTagFromCustomType()推断两侧标签若 LHS 为!!timestamp或字符串可被parseDateTime解析则走日期时间比较若两侧均为!!int走整数比较parseInt64若一侧为!!int、一侧为!!float走浮点比较strconv.ParseFloat若两侧均为!!str走字符串比较Go 原生字符串比较若涉及!!null走空值语义分支其余情况返回%v not yet supported for comparison错误。值得注意的类型推断细节yq 会在比较前尝试把字符串解析为日期时间这意味着即使 YAML 中没有显式!!timestamp标签只要值符合当前日期时间布局就会被当作日期时间参与比较。默认布局由context.go中的GetDateTimeLayout()提供未显式设置时返回time.RFC3339。数字比较整数与浮点数基本用法给定sample.ymla: 5 b: 4执行yq .a .b sample.yml输出true严格大于即返回true如果两侧相等a: 5, b: 5返回false而返回trueyq .a .b sample.yml输出true整数与浮点混合比较从源码分支可以看到当两侧标签满足(!!int || !!float) (!!int || !!float)时yq 会把两侧值统一用strconv.ParseFloat转成 64 位浮点再比较因此整数与浮点数可以混合比较。测试用例见operators_compare_test.go验证了a: 5.2, b: 4.1时.a .b为true、.a .b为false等场景。四个操作符在数字比较下的行为可总结为表达式语义a5, b4a5, b5.a .b严格大于truefalse.a .b严格小于falsefalse.a .b大于等于truetrue.a .b小于等于falsetrue字符串比较按字节码排序字符串按**字节码bytecode**顺序比较即逐字节比较 UTF-8 编码后的值而非按字典序或本地化规则。这一点在文档中有明确说明测试用例中也原样继承了该描述见operators_compare_test.go。给定sample.ymla: zoo b: apple执行yq .a .b sample.yml输出true因为z的字节码大于a的字节码所以zoo apple。对应实现是operator_compare.go中的 Go 原生字符串比较分支先处理OrEqual相等情形再按方向返回lhs.Value rhs.Value或lhs.Value rhs.Value。字符串相等时的判定与数字一致与返回false与返回true测试见operators_compare_test.go。日期时间比较默认 RFC3339支持自定义布局基本用法日期时间比较默认采用RFC3339格式2006-01-02T15:04:05Z07:00。给定sample.ymla: 2021-01-01T03:10:00Z b: 2020-01-01T03:10:00Z执行yq .a .b sample.yml输出true实现上compareDateTime见operator_compare.go先用当前布局分别解析两侧时间然后借助 Go 标准库time.Time的Equal、After、Before方法完成判定if prefs.OrEqual lhsTime.Equal(rhsTime) { return true, nil } if prefs.Greater { return lhsTime.After(rhsTime), nil } return lhsTime.Before(rhsTime), nil自定义日期时间格式当你的数据不是 RFC3339 格式时可以先用with_dtf操作符设置自定义解析布局再进行比较。日期时间布局使用 Go 的参考时间格式如2006-01-02、Monday, 02-Jan-06 at 3:04PM等与日期时间操作符文档中描述的机制一致yq with_dtf(2006-01-02; .a .b) sample.yml需要说明的是比较逻辑中日期时间的识别策略是先看 LHS 标签是否为!!timestamp若 LHS 是字符串则尝试用当前上下文布局解析见operator_compare.go解析成功即视为日期时间比较。因此两侧字符串的格式必须能被同一布局解析否则会回退到字符串字节码比较或报错。null 语义两侧为空与单侧为空比较操作符对 null 有明确的语义约定这在compare.md中给出了两个经典示例。两侧都是 null为 false为 trueyq --null-input .a .b输出false而yq --null-input .a .b输出true对应源码中的两条分支见operator_compare.go} else if lhsTag !!null rhsTag !!null prefs.OrEqual { return true, nil } else if lhsTag !!null || rhsTag !!null { return false, nil }即两侧都是 null 时只有含「等于」语义的、返回true而单侧为 null 时无论哪个比较操作符都返回false。单侧为 null一律为 false测试用例进一步验证了单侧 null 的行为见operators_compare_test.go当文档只有a: 5而表达式为.a .b时由于.b不存在null结果恒为false.a .b同样为false。此外如果 LHS 与 RHS 都是空lhs nil rhs nil闭包函数直接返回prefs.OrEqual对应的布尔值见operator_compare.go。与相关操作符协同实战比较操作符很少单独使用最常见的场景是与以下操作符组合配合 select 过滤select 文档指出select 用于按布尔表达式过滤数组和映射。例如筛选出价格超过阈值的条目yq .[] | select(.price 100) sample.yml配合等于/不等于操作符与!是另一个维度的比较判定相等性而非大小常与、并列使用。完整的等于/不等于语义见equals.md。二者在操作注册表中同为优先级 40 的二元操作见operation.go。配合布尔操作符比较结果可以与and、or、not组合成复合条件再交给 select 使用例如yq .[] | select(.a 10 and .b 100) sample.yml布尔操作符的完整语义含any、all、any_c、all_c见boolean-operators.md。延伸min 与 max 的底层复用比较机制还支撑了min与max操作符。在operator_compare.go中minOperator与maxOperator通过superlativeByComparison复用同一compare函数对序列中的每个候选节点两两比较min使用compareTypePref{Greater: false}max使用compareTypePref{Greater: true}。测试见operators_compare_test.go例如yq min sample.yml # 文档为 [99, 16, 12, 6, 66] 时输出 6 yq max sample.yml # 输出 99min/max同样支持字符串序列按字节码取最小/最大与空数组无输出详细用例可参考官方 min 文档。边界与限制映射与数组不支持比较当 LHS 为 MappingNode 或 SequenceNode 时比较闭包直接返回错误maps not yet supported for comparison或arrays not yet supported for comparison见operator_compare.go。类型不匹配当 LHS 为标量而 RHS 不是 ScalarNode 时会返回形如%v (%v) cannot be subtracted from %v的错误当两侧标签组合无法落入任何已支持分支时返回%v not yet supported for comparison。日期格式一致性日期时间比较依赖两侧值能被同一布局解析格式不匹配时可能回退为字符串比较或解析失败使用自定义格式时务必通过with_dtf保证布局一致。null 的确定性单侧为 null 时四个比较操作符恒为false这是设计约定而非未定义行为编写筛选条件时需留意缺失字段不会「误伤」结果。掌握这些边界行为后你就可以放心地在自动化脚本与 CI 流程中用 yq 的比较操作符对 YAML/JSON/XML/CSV/TOML 等格式的数据做精确的条件筛选了。【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考