yq 布尔操作符完全指南:or / and / not / any / all / any_c / all_c 与 select 过滤实战
【免费下载链接】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 处理器)中的全部布尔操作符:二元操作符or、and,一元操作符not,作用于数组的any、all,以及带条件表达式的any_c、all_c。你将掌握每个操作符的语法、真值判定规则、与select组合过滤节点的典型用法,并通过源码实现理解短路求值、空数组语义等底层原理,最终能够在日常数据处理中写出准确、高效的过滤与判断表达式。
布尔操作符一览
| 操作符 | 参数个数 | 说明 |
|---|---|---|
or | 2 | 左右两侧取其一为真即为真(真值表同逻辑或) |
and | 2 | 左右两侧同时为真才为真(真值表同逻辑与) |
not | 1(前置管道) | 将布尔值取反,true 变 false、false 变 true |
any | 0 | 数组序列中存在任一true元素则返回true |
all | 0 | 数组序列中所有元素均为true才返回true |
any_c(condition) | 1(条件表达式) | 对每个元素应用条件表达式,任一元素满足即返回true |
all_c(condition) | 1(条件表达式) | 对每个元素应用条件表达式,所有元素满足才返回true |
其中or、and是二元操作符,在 operation.go 中定义,参数个数为 2;not、any、all定义见 operation.go 与 operation.go;any_c、all_c各接收 1 个条件表达式参数。词法层面,这些关键字通过 lexer_participle.go 与 lexer_participle.go 中的simpleOp注册。
从源码结构可以推断:or/and的优先级为 20,低于not、any、all、any_c、all_c的优先级 50,因此在组合表达式中布尔运算会先完成高优先级部分的求值,再与比较操作符(如==)协同工作。
or操作符:两侧任一为真即为真
or接收两个参数并返回布尔结果。最直接的验证方式是利用--null-input标志,不读取任何输入文件,直接对表达式求值:
yq --null-input 'true or false'输出:
trueor的实现位于 operator_booleans.go,它通过crossFunctionWithPrefs装配以下行为:
CalcWhenEmpty: true:当左侧表达式无匹配节点(如.a在文档中不存在)时,仍然参与计算,此时右侧结果直接决定返回值。测试用例b: hi文档上执行.a or .c得到false正是这一行为(见 operator_booleans_test.go);LhsResultValue: returnLHSWhen(true):短路求值——只要左侧为真,就直接返回true,不再计算右侧。测试用例true or test(3)输出true且不会执行test(3)(见 operator_booleans_test.go),避免了不必要的求值与副作用。
and操作符:两侧同为真才为真
yq --null-input 'true and false'输出:
falseand的实现见 operator_booleans.go,同样使用CalcWhenEmpty: true,但LhsResultValue换成returnLHSWhen(false):只要左侧为假,立即返回false。对应测试用例false and test(3)输出false(见 operator_booleans_test.go)。
两者共享的底层框架是crossFunctionWithPrefs(见 operators.go)与resultsForRHS(见 operators.go):先求值左侧表达式,若左侧满足短路条件则跳过右侧求值,否则逐一求值右侧并合并结果。这套机制保证了布尔运算既能正确处理"左值缺失"的场景,又能像主流编程语言一样短路。
not操作符与 yq 的真值判定规则
not翻转布尔值:
yq --null-input 'true | not'输出:
falseyq --null-input 'false | not'输出:
truenot的实现遍历所有匹配节点并逐一取反(见 operator_booleans.go),而"何为真"由核心函数isTruthyNode决定(见 operator_booleans.go),规则如下:
| 节点类型 | 判定结果 |
|---|---|
null(!!null标签) | false(唯一被视为假的非布尔类型) |
标量且标签为!!bool | 值等于y/yes/on/true(不区分大小写)时为真,否则为假 |
| 其他一切节点(字符串、数字、映射、数组等) | true |
这意味着字符串、数字乃至0都被视为"真",只有null和显式的false布尔值才是"假"。文档与测试用例完整覆盖了这些边界情况:
yq --null-input '"cat" | not' # 字符串视为真,not 后为 false yq --null-input '"" | not' # 空字符串同样视为真,not 后为 false yq --null-input '1 | not' # 数字视为真,not 后为 false yq --null-input '0 | not' # 0 也视为真,not 后为 false yq --null-input '~ | not' # null 视为假,not 后为 true四段输出依次为false、false、false、false、true(对应测试见 operator_booleans_test.go)。值得注意的是,yq 在判定布尔标量时遵循 YAML 1.1/1.2 兼容的布尔词集合,yes、on、y均被视为真(isTruthyNode中strings.EqualFold不区分大小写)。
any:数组中是否存在真值
any作用于数组序列,只要存在任意一个true值即返回true。
给定 sample.yml:
- false - true执行:
yq 'any' sample.yml输出:
true对于空数组:
[]执行同样的yq 'any' sample.yml,输出:
false从源码看,any的实现为anyOperator(见 operator_booleans.go):它要求当前匹配节点必须是序列(SequenceNode),否则直接报错any only supports arrays;随后调用findBoolean(true, ...)遍历元素,一旦发现真值即返回true,空数组自然得到false。
all:数组是否全为真值
all要求数组中的所有元素都为true才返回true。
给定 sample.yml:
- true - true执行:
yq 'all' sample.yml输出:
true与any相反,all对空数组返回true(数学上的"空真"语义):
[]执行yq 'all' sample.yml,输出:
trueallOperator(见 operator_booleans.go)同样限定输入必须是数组,它调用findBoolean(false, ...):只要遍历到任一非真元素即提前返回false,遍历完仍未发现假值则整体为true——这正是空数组返回true的原因。
any_c与all_c:带条件表达式的数组判定
any_c(condition)与all_c(condition)是any/all的增强版:它们接收一个条件表达式,对数组中的每个元素分别求值,再判断整体结果。
给定 sample.yml:
a: - rad - awesome b: - meh - whatever执行:
yq '.[] |= any_c(. == "awesome")' sample.yml输出:
a: true b: false再给定 sample.yml:
a: - rad - awesome b: - meh - 12执行:
yq '.[] |= all_c(tag == "!!str")' sample.yml输出:
a: true b: false第二个例子中,a的两个元素rad、awesome的tag都是!!str(字符串),因此all_c为真;而b中混入了数字12(tag为!!int),整体判定为假。
其底层机制在findBoolean(见 operator_booleans.go):当条件表达式存在时,先对每个数组元素调用GetMatchingNodes求值条件,取首个匹配结果参与真值判断;若某元素对条件无匹配结果,则该元素被跳过。需要留意的是,官方文档明确指出:在 jq 中可以直接把条件传给any/all,而 yq 目前尚不支持这种写法,必须显式使用any_c/all_c。
any_c/all_c的条件结果同样可以绑定到变量复用,测试用例any_c(.name == "harry") as $c | $c返回false(见 operator_booleans_test.go),说明其返回值与普通表达式一致,可参与后续管道处理。
与select组合:按布尔条件过滤节点
布尔操作符最常见的实战场景是与select操作符组合,过滤出满足复合条件的节点。
给定 sample.yml:
- a: bird b: dog - a: frog b: bird - a: cat b: fly执行:
yq '[.[] | select(.a == "cat" or .b == "dog")]' sample.yml输出:
- a: bird b: dog - a: cat b: fly这里.[]展开数组的每个元素,select(...)依据括号内的布尔表达式决定是否保留节点,外层[...]将过滤结果重新收集为数组。or连接的两个==比较(equals/not equals 操作符,见 equals.md)正是布尔操作符与比较操作符协作的典型形态。select的详细用法可参考 select.md,而>=、<等比较操作符见 compare.md。
反向过滤也常用not配合:测试用例select((.a and .b) | not)(见 operator_booleans_test.go)展示了"当某复合条件不满足时保留节点"的写法,等价于过滤掉同时满足.a与.b的节点。
常见陷阱:YAML 1.2 中yes/no是字符串
在处理布尔数据时,一个容易踩坑的点是:根据 YAML 1.2 规范(见规范 10.2.1.2 Boolean 章节),yes/no不再被解析为布尔值,而是普通字符串。
给定 sample.yml:
- yes - no执行:
yq '.[] | tag' sample.yml输出:
!!str !!str两个元素的tag都是!!str,证明yes、no被当作字符串处理。这意味着如果业务数据依赖yes/no表达布尔语义,需要先将其转换为真正的布尔值(例如配合== "yes"比较或转换为字符串后再判定),否则布尔操作符按isTruthyNode的规则会把非空字符串一律视为真,产生与直觉相悖的结果。
源码脉络:从词法到求值的完整链路
布尔操作符的完整调用链可归纳为:
- 词法解析:lexer_participle.go 中
simpleOp将or、and、not、any、all、any_c、all_c识别为对应操作符类型; - 操作符定义:operation.go 与 operation.go 声明参数个数、优先级并绑定处理函数(
orOperator、andOperator、notOperator、anyOperator、allOperator); - 求值实现:operator_booleans.go 实现全部求值逻辑,其中真值判定收敛于
isTruthyNode,二元运算收敛于crossFunctionWithPrefs(见 operators.go),数组判定收敛于findBoolean; - 测试验证:operator_booleans_test.go 通过
booleanOperatorScenarios表驱动测试覆盖了本文全部示例,包括短路求值、空数组语义、缺失字段处理、真值边界(字符串、数字、0、null)等 30 余个场景,运行go test ./pkg/yqlib/ -run TestBooleanOperatorScenarios即可复现验证。
小结
yq 的布尔操作符家族虽小,却覆盖了过滤场景的绝大多数需求:or/and负责复合条件组合并支持短路求值,not负责取反,any/all处理纯布尔数组,any_c/all_c以条件表达式驱动数组判定,再配合select即可完成任意复杂度的节点筛选。理解 yq 独有的真值判定规则(仅null与false为假、非空字符串与数字均为真)以及 YAML 1.2 的yes/no字符串语义,是写出无 bug 过滤表达式的关键前提。
【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考