
yq 字符串操作符完全指南match、capture、sub、interpolation 与 bash 换行实战【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yqyq 除了解析与重构 YAML/JSON/XML 等结构化数据外还提供了一组专门用于处理标量文本的操作符正则匹配match、capture、test、替换sub、大小写转换upcase/downcase、拼接与拆分join/split、修剪trim与强制转字符串to_string以及表达式字符串插值\(.expr)。本文基于仓库文档 string-operators.md 逐条展开所有用法与示例并结合 operator_strings.go 等源码解析底层实现Go 原生 RE2 正则、字符串插值器、类型守卫帮助你既会写表达式也明白这些操作在 yq 引擎内部是如何执行的。一、字符串操作符总览从操作符注册文件 operation.go 可以看到字符串类操作符统一注册在 yq 的算子表中每个算子都绑定了优先级Precedence与处理函数Handler操作符参数个数功能源码处理函数match(regEx)1返回子串匹配详情可加g全局标志matchOperatorcapture(regEx)1将命名捕获组输出为映射mapcaptureOperatortest(regEx)1返回true/false类似 jq 的 testtestOperatorsub(regEx, replacement)1block替换匹配到的子串replacement 可引用捕获组substituteStringOperatorupcase/ascii_upcase0转大写支持 UnicodechangeCaseOperatordowncase/ascii_downcase0转小写支持 UnicodechangeCaseOperatorjoin(sep)1将数组用分隔符拼接为字符串joinStringOperatorsplit(sep)1将字符串按分隔符拆分为数组splitStringOperatortrim0去除首尾空白trimSpaceOperatorto_string/to_str0任意节点强制转为字符串toStringOperator...\(expr)...-字符串插值stringInterpolationOperator其中upcase/downcase实际上是CHANGE_CASE操作符的两种配置别名在词法分析器 lexer_participle.go 中通过changeCasePrefs{ToUpperCase: true/false}参数化注册to_string同样接受to_str写法to_?string正则。正则语法基础所有正则类操作符底层都使用 Go 原生regexp包即 RE2 语法支持(?)修饰符、命名捕获组(?Pname...)等不支持回溯。一个实用技巧如需忽略大小写匹配在正则前加(?i)前缀例如test((?i)cats)。这一点在源码中有明确的约束体现——extractMatchArgumentsoperator_strings.go会显式拒绝match(cat; i)这种 jq 风格的i标志并报错提示改用match((?i)cat)写法。二、match(regEx)获取子串匹配详情match返回一个映射包含string匹配到的子串、offset起始偏移、length长度和captures捕获组列表四个字段。基本匹配给定sample.ymlfoo bar fooyq match(foo) sample.yml输出string: foo offset: 0 length: 3 captures: []不带全局标志时match只返回第一次匹配。源码 getMatches 中的逻辑是Global为false时调用FindStringSubmatch单次匹配为true时调用FindAllStringSubmatch/FindAllStringSubmatchIndex全部匹配。全局标志 g在第二个参数中传入g即可匹配全部出现注意此时结果是多个映射通常需要[...]收集成数组yq [match(cat; g)] sample.yml # sample.yml 内容为: cat cat- string: cat offset: 0 length: 3 captures: [] - string: cat offset: 4 length: 3 captures: []解析细节见 extractMatchArguments如果第二个参数含字符g则置matchPreferences.Global true出现i直接报错其他无法识别的参数也会报错提示参考文档。忽略大小写的匹配yq [match((?i)foo; g)] sample.yml # sample.yml 内容为: foo bar FOO- string: foo offset: 0 length: 3 captures: [] - string: FOO offset: 8 length: 3 captures: []捕获组capture groups正则中的括号分组会逐个进入captures数组每个捕获同样携带string/offset/lengthyq [match((ab)(c); g)] sample.yml # sample.yml 内容为: abc abc- string: abc offset: 0 length: 3 captures: - string: ab offset: 0 length: 2 - string: c offset: 2 length: 1 - string: abc offset: 4 length: 3 captures: - string: ab offset: 4 length: 2 - string: c offset: 6 length: 1命名捕获组使用(?Pbar123...)时对应的捕获项会额外带上name字段yq [match(foo (?Pbar123bar)? foo; g)] sample.yml # 内容为: foo bar foo foo foo- string: foo bar foo offset: 0 length: 11 captures: - string: bar offset: 4 length: 3 name: bar123 - string: foo foo offset: 12 length: 8 captures: - string: null offset: -1 length: 0 name: bar123注意第二个匹配中可选组未命中源码 addMatch 中约定offset 0表示该组没有匹配此时string字段输出为null、offset为-1与 jq 行为保持一致。类型守卫match只能作用于字符串。源码 matchOperator 会用guessTagFromCustomType()检查节点 tag非!!str时报错并给出提示Hint: Most often youll want to use | over for this operation——即提醒你在原地替换|场景下才对当前标量做字符串操作。三、capture(regEx)命名捕获组直接变 mapcapture与match共用同一套参数解析支持g区别在于它把命名捕获组输出为一个映射键即组名值即捕获内容——在很多场景下比match更简洁。yq capture((?Pa[a-z])-(?Pn[0-9])) sample.yml # 内容为: xyzzy-14a: xyzzy n: 14注意n的值虽然看起来是数字但 YAML 会保留其字符串属性双引号输出。源码实现见 capture逐个命名组取出子匹配值未命中的组offset 为 -1会写入null值测试用例 operator_strings_test.go 中有对应验证bar123: null。四、test(regEx)只返回布尔值与 jq 的test语义一致它按match的方式匹配但只返回true/false不输出完整匹配详情。给定sample.yml- cat - dogyq .[] | test(at) sample.ymltrue false源码 testOperator 的实现非常直接对每个候选节点调用regEx.FindStringSubmatch用len(matches) 0构造布尔节点。五、sub(regEx, replacement)替换匹配的子串sub替换字符串中所有匹配的子串。第一个参数是用于匹配的正则第二个参数是替换内容可以在替换内容中引用第一个正则的捕获组。普通替换yq .a | sub(dogs, cats) sample.yml # sample.yml 内容为: a: dogs are greata: cats are great注意这里使用|assign-update在当前字符串值的上下文中执行替换后写回原路径而不是新建一个结果。带捕获组的替换# sample.yml a: cat b: heatyq .[] | sub((a), ${1}r) sample.ymla: cart b: heart替换串${1}r中的${1}引用了正则的第一个捕获组。源码 substitute 直接调用regex.ReplaceAllString(original, replacement)因此 Go 正则的全部替换语法${1}、$name等都可用。sub的两个参数分别作为 block 的 LHS/RHS 求值见 getSubstituteParameters。自定义标签的伪字符串当 YAML 中出现自定义 tag如!horse时yq 会尝试解码其底层类型。对底层是字符串的节点字符串操作符依然有效且 tag 会被保留# sample.yml a: !horse cat b: !goat heatyq .[] | sub((a), ${1}r) sample.ymla: !horse cart b: !goat heart这在测试用例中有对应验证operator_strings_test.go也解释了为什么类型守卫用的是guessTagFromCustomType()而不是直接比较 tag——它会穿透自定义 tag 判断真实底层类型。六、upcase / downcase支持 Unicode 的大小写转换upcase转大写、downcase转小写均支持 Unicode 字符。# sample.yml águayq upcase sample.ymlÁGUA# sample.yml ÁgUAyq downcase sample.ymlágua源码 changeCaseOperator 使用 Go 的strings.ToUpper/strings.ToLower这两个函数本身是按 Unicode 逐 rune 处理的所以á、Á等非 ASCII 字符也能正确转换。与upcase同注册的还有ascii_upcase别名见 lexer_participle.go 的upcase|ascii_?upcase定义。非字符串节点同样会被类型守卫拦截并报错。七、join / split字符串与数组的互转join(sep)数组拼成字符串# sample.yml - cat - meow - 1 - null - trueyq join(; ) sample.ymlcat; meow; 1; ; true注意两点行为null元素被替换为空串参与拼接输出中1后面紧跟一个空段join只能作用于数组源码 joinStringOperator 检查node.Kind ! SequenceNode时直接报错cannot join with ..., can only join arrays of scalars。split(sep)字符串拆成数组yq split(; ) sample.yml # sample.yml 内容为: cat; meow; 1; ; true- cat - meow - 1 - - true拆出的每个元素都是字符串类型所以1和true会被双引号包裹以保持字符串语义。若字符串中不存在分隔符返回只含原字符串的单元素数组yq split(; ) sample.yml # 内容为: word- word源码 split 基于 Go 的strings.Split普通字符串分隔不是正则空字符串输入返回空序列null节点则被直接跳过splitStringOperator。八、trim去除首尾空白# sample.yml - cat - dog - cow cow - horseyq .[] | trim sample.ymlcat dog cow cow horse源码 trimSpaceOperator 使用strings.TrimSpace去除首尾空白同时保留原节点的 YAML 样式Style非字符串节点报错cannot trim ...。注意trim只影响标量的首尾空白中间空白如cow cow中间的空格保持不变。九、to_string任意节点强制转为字符串to_string或to_str把任意节点序列化为 YAML 文本字符串。# sample.yml - 1 - true - null - ~ - cat - an: object - - array - 2yq .[] | to_string sample.yml- 1 - true - null - ~ - cat - an: object - - array\n- 2从输出可以看出规则原本就是字符串的cat保持原样标量1、true、null、~变成带引号的字符串字面量映射和数组则被编码成 YAML 片段字符串an: object、含换行符的- array\n- 2。实现见 toStringOperator!!str节点原样保留其他标量取node.Value映射/序列则走 encodeToYamlString 按当前配置的缩进重新编码成字符串并去掉末尾换行chomper。文档同时提醒如果想让输出的标量保持引号包裹可传--unwrapScalarfalse或-rf阻止 yq 输出时拆包纯字符串标量。十、字符串插值Interpolation表达式里嵌入数据在双引号字符串中\(expression)会执行一个 yq 表达式并把结果拼入字符串。给定sample.ymlvalue: things another: stuffyq .message I like \(.value) and \(.another) sample.yml输出value: things another: stuff message: I like things and stuff插值非字符串节点当被插值的路径指向映射时yq 会将其编码为 YAML 字符串再拼接# sample.yml value: an: appleyq .message I like \(.value) sample.ymlvalue: an: apple message: I like an: apple插值器的实现细节插值的核心在 interpolate它逐字符扫描字符串遇到\\(进入表达式状态用括号计数处理嵌套括号支持\( (.value) )这种内部带括号的表达式遇到不匹配的)则按普通字符处理遇到未闭合的插值会打印告警unclosed interpolation string, skipping interpolation并原样输出字符串——测试用例 operator_strings_test.go 覆盖了未闭合插值与转义导致未闭合两种边界。其他由测试用例确认的行为不插值Hi (.value)没有反斜杠原样输出Hi (.value)转义Hi \\(.value)中的插值被转义输出字面量Hi \(.value)全局开关operator_strings.go 中的StringInterpolationEnabled为false时stringInterpolationOperator直接把字符串当作纯文本处理不再求值。十一、Bash 换行、字符串块与 strenvBash 会吃掉宝贵的末尾换行符导致设置带换行的字符串很棘手。尤其是$( exp )命令替换会裁剪末尾换行。例如要得到这样的 YAMLa: | cat用$( ... )是不行的因为末尾换行会被裁掉m$(echo cat\n) yq -n .a strenv(m) # 输出 a: cat而用printf -v可以保住换行printf -v m cat\n ; m$m yq -n .a strenv(m) # 输出 a: | cat同样可以使用多行字符串变量mcat yq -n .a strenv(m) # 输出 a: | cat如果要从文件读取内容并希望保留末尾换行推荐这种读法IFS read -rd output (cat my_file) output$output ./yq .data.values strenv(output) first.yml其中用到的strenv(name)是环境变量操作符用于把环境变量以字符串形式引入表达式。它在词法层通过专门的 token 识别lexer_participle.go 中strenv\([^\)]\)直接匹配strenv(变量名)写法实现见 operator_env.go。文档中strenv(m)的参数是裸标识符非引号字符串这正是其词法定义所支持的用法。需要注意当启用安全模式security mode时strenv会因涉及系统环境变量访问而被拒绝相关限制在 operator_env_test.go 中有对应测试。十二、文档与源码的对应关系测试即文档一个值得注意的工程细节本文依据的文档 string-operators.md 中的每个输入→表达式→输出示例都能在测试文件 operator_strings_test.go 的stringsOperatorScenarios表里找到逐字对应项如match(foo)、[match(cat; g)]、.a | sub(dogs, cats)、.[] | to_string等且测试函数最后调用documentOperatorScenarios(t, string-operators, ...)把文档与测试场景做一致性校验。这意味着文档中的每一个输出示例都是经过自动化测试验证的真实行为而非手写的示意测试表里还包含大量skipDoc: true的补充场景自定义 tag、未命中匹配返回空、split(; )[]展开、to_string的行内数组写法等覆盖范围比文档展示的更广如果你发现某个示例在当前版本运行结果不同优先以该测试文件的预期值为准它就是行为契约。总结yq 的字符串操作符围绕一条清晰的主线展开RE2 正则三件套match/capture/test负责看sub负责改join/split/trim/upcase/downcase/to_string负责整理插值与strenv负责把外部数据注入表达式。所有操作符都通过guessTagFromCustomType做字符串类型守卫因此自定义 tag 的字符串节点也能正常处理并注册在 operation.go 的算子表中。日常使用时记住两个高频技巧即可全局匹配加g[match(pat; g)]、忽略大小写加(?i)前缀而带换行的字符串注入则优先用printf -v或IFS read -rd 保住末尾换行再经strenv写入 YAML。【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考