yq 编码与解码运算符完全指南:从 to_json、from_yaml 到 @base64 的嵌入式格式处理实战
【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq
导读
在真实项目中,YAML 文档里经常嵌套着被字符串化的 JSON、YAML、Properties、CSV 甚至 XML 内容,例如配置项里存放一段序列化后的 JSON、CI 流水线里嵌入一段 shell 片段。yq 的 Encode/Decode 运算符族正是为这类场景设计的:Encode 运算符把管道传入的对象结构编码为指定格式的字符串,Decode 运算符则反向把格式化字符串解析回对象结构。本文以 yq 仓库的 encode-decode 官方文档 为骨架,结合 operator_encoder_decoder.go 与 lexer_participle.go 的源码实现,带你掌握全部 9 种格式的编码/解码用法、indent 参数细节与嵌套字符串的原地更新技巧。
核心概念:编码与解码是一对可逆操作
Encode 运算符负责把对象结构转成字符串,Decode 运算符负责把字符串还原成对象结构,两者互为逆操作。它们都是标准运算符,可以出现在管道(pipe)的任意位置,也可以和赋值运算符(=、|=)组合完成"解码 → 修改 → 再编码"的整段流程。
下表是官方文档给出的完整格式速查表,两列分别对应各格式的解码与编码入口:
| 格式 | Decode(字符串 → 对象) | Encode(对象 → 字符串) |
|---|---|---|
| YAML | from_yaml/@yamld | to_yaml(i)/@yaml |
| JSON | from_json/@jsond | to_json(i)/@json |
| Properties | from_props/@propsd | to_props/@props |
| CSV | from_csv/@csvd | to_csv/@csv |
| TSV | from_tsv/@tsvd | to_tsv/@tsv |
| XML | from_xml/@xmld | to_xml(i)/@xml |
| Base64 | @base64d | @base64 |
| URI | @urid | @uri |
| Shell | — | @sh |
注意表内几个细节:
to_yaml(i)、to_json(i)、to_xml(i)中的(i)表示可以传一个缩进参数;而@yaml、@json、@xml这类以@开头的速记形式不带参数,行为固定(详见下文"缩进参数"一节)。- 解码没有缩进概念,因此
from_*与@*d形式完全等价。 - Shell 只有编码(
@sh),没有对应的解码运算符——因为 shell 字符串无法无歧义地解析回对象。
从源码角度看,这些运算符在 lexer_participle.go 中被定义为一组带正则的 token 规则,例如:
to_?yaml|@yaml→ 使用 2 空格缩进的 YAML 编码(encodeWithIndent(YamlFormat, 2));to_?json→ 2 空格缩进的 JSON 编码,而@json是0 缩进的 JSON 编码(encodeWithIndent(JSONFormat, 0));to_?yaml\([0-9]+\)→ 通过encodeParseIndent解析括号里的缩进数字,这正是to_yaml(8)能被解析的底层原因。
这也解释了官方文档"Pass in a 0 indent to print json on a single line"的说法:@json在词法层面就固定为indent = 0。
底层实现:encodeOperator 与 decodeOperator
编码与解码的运行时逻辑集中在 operator_encoder_decoder.go:
encodeToString(L34-L46):根据目标格式调用configureEncoder构造对应 Encoder,configureEncoder(L12-L32)会复制当前全局偏好(如ConfiguredYamlPreferences、ConfiguredJSONPreferences),再覆盖Indent为本次调用传入的缩进值,最后通过Printer输出为字符串。这也是为什么to_yaml输出的字符串与 CLI 顶层-o=yaml输出风格一致。encodeOperator(L56-L93):遍历每个匹配节点,把编码结果生成为!!str类型的标量节点(CreateReplacement(ScalarNode, "!!str", stringValue))。它还会做两件收尾工作:一是当format == JSONFormat && indent == 0或格式为 CSV/TSV 时,用chomper正则(\n+$)去掉结尾换行,所以@json、@csv、@tsv输出是"干净的单行";二是借助decoded: <key>变量记录原始字符串是否以换行结尾,从而在"解码后重新编码"时保持单行/多行形态(这正是下文"更新单行/多行 YAML 字符串"两节行为差异的来源)。decodeOperator(L100-L131):调用preferences.format.DecoderFactory()获得 Decoder,对候选节点的字符串值做decoder.Init+decoder.Decode,并把结果节点挂回原 key 与 parent,从而替换原字符串值。编码与解码使用的格式注册表在 format.go 中,其中 YAML、JSON、Properties、CSV、TSV、XML、Base64、URI 均为"可编码也可解码"的Format,而ShFormat的DecoderFactory为nil,这就是 Shell 无解码运算符的源码级原因。
JSON:字符串化与反序列化
JSON 是文档中演示最多的格式,因为它最常见于"配置里嵌配置"的场景。
把对象编码为 JSON 字符串
给定sample.yml:
a: cool: thing执行:
yq '.b = (.a | to_json)' sample.yml输出:
a: cool: thing b: | { "cool": "thing" }to_json默认使用 2 空格缩进,因此输出是带换行的块状标量(|)。
单行 JSON:to_json(0) 与 @json
如果你希望 JSON 输出在一行内(例如要直接作为另一个程序的参数),传入0缩进:
yq '.b = (.a | to_json(0))' sample.yml输出:
a: cool: thing b: '{"cool":"thing"}'等价地,@json就是 0 缩进的速记形式:
yq '.b = (.a | @json)' sample.yml输出与上例完全相同。正如前文源码分析,@json在词法层即绑定indent = 0,而to_json绑定indent = 2,二者语义略有差异,速记并不完全等价于无参的to_json。
解码 JSON 字符串
给定sample.yml:
a: '{"cool":"thing"}'执行:
yq '.a | from_json | ... style=""' sample.yml输出:
cool: thing这里... style=""是 style 运算符 的递归形式,用于清除 JSON 解码后残留的双引号(JSON 字符串节点默认带双引号样式)。官方文档特别提醒:JSON 是 YAML 的子集,from_json解码结果在语法上合法,但若要得到地道的 YAML 观感,建议接上 style 运算符清理。仓库测试 operator_encoder_decoder_test.go 以表格驱动方式逐一验证了这些场景。
YAML:to_yaml 的缩进控制与原地更新
编码为 YAML 字符串(默认 2 空格)
给定sample.yml:
a: cool: bob: dylan执行:
yq '.b = (.a | to_yaml)' sample.yml输出:
a: cool: bob: dylan b: | cool: bob: dylan文档明确指出to_yaml的缩进默认为 2,与configureEncoder中prefs.Indent = indent的默认值 2 一致。
自定义缩进:to_yaml(8)
缩进作为第一个参数传入:
yq '.b = (.a | to_yaml(8))' sample.yml输出:
a: cool: bob: dylan b: | cool: bob: dylan注意b的值依然是 YAML 块标量,只是内嵌 YAML 的每级缩进变成了 8 空格——这在生成"对齐美观"的嵌套配置模板时非常实用。
解码 YAML 字符串
给定sample.yml:
a: 'foo: bar'执行:
yq '.b = (.a | from_yaml)' sample.yml输出:
a: 'foo: bar' b: foo: bar更新多行内嵌 YAML:.a |= (from_yaml | ... | to_yaml)
这是 encode/decode 家族最常用的实战组合:先解码、再修改、最后重新编码,且用|=(更新运算符)原地写回。给定多行块标量:
a: | foo: bar baz: dog执行:
yq '.a |= (from_yaml | .foo = "cat" | to_yaml)' sample.yml输出:
a: | foo: cat baz: dog更新单行内嵌 YAML
同样的表达式作用于单行字符串(a: 'foo: bar'):
yq '.a |= (from_yaml | .foo = "cat" | to_yaml)' sample.yml输出:
a: 'foo: cat'对比可见:多行输入保持|块标量形态,单行输入则保持单行引号形态。这正是 encodeOperator 中decoded:变量机制在起作用——decodeOperator先把原始候选节点存入context.SetVariable("decoded: "+candidate.GetKey(), ...),编码时再取出比对,若原始值不是以换行结尾且编码结果恰为单行,就剔除多余的末尾换行。
Properties:.properties 配置的互转
编码为 props 字符串
给定sample.yml:
a: cool: thing执行:
yq '.b = (.a | @props)' sample.yml输出:
a: cool: thing b: | cool = thing解码 props 字符串
给定sample.yml:
a: |- cats=great dogs=cool as well执行:
yq '.a |= @propsd' sample.yml输出:
a: cats: great dogs: cool as well从 format.go 可见,Properties 使用NewPropertiesEncoder与NewPropertiesDecoder,配合 properties.go 中=分隔符与多行值的解析逻辑。若想了解 Properties 在整文件层面的转换(如yq -p=props -o=yaml),可参考 properties 使用文档。
CSV 与 TSV:标量数组、数组的数组与首行表头
CSV 与 TSV 的编解码在源码中共用CSVFormat/TSVFormat,编码器同为NewCsvEncoder(只是偏好不同),解码器同为NewCSVObjectDecoder。官方文档要求查阅 CSV/TSV 使用文档 了解其接受的输入形态,要点如下:
- 编码支持两类输入:同构扁平对象数组(第一行对象包含全部 key)、标量数组的数组(scalars 指字符串、数字、布尔值);
- 解码假设第一行是表头,其后每行成为对象数组中的一个元素,表头作为 key。
解码 CSV 字符串
给定sample.yml:
a: |- cats,dogs great,cool as well执行:
yq '.a |= @csvd' sample.yml输出:
a: - cats: great dogs: cool as well解码 TSV 字符串
TSV 只是把分隔符换成 Tab。给定:
a: |- cats dogs great cool as well执行:
yq '.a |= @tsvd' sample.yml输出:
a: - cats: great dogs: cool as well编码标量数组为 CSV
给定:
- cat - thing1,thing2 - true - 3.40执行:
yq '@csv' sample.yml输出:
cat,"thing1,thing2",true,3.40注意元素thing1,thing2内含逗号,被自动加引号包裹,而布尔true、数字3.40保持原样——这得益于 CSV 编码器对字符串、数字、布尔值的类型感知。
编码"数组的数组"为 CSV / TSV
给定:
- - cat - thing1,thing2 - true - 3.40 - - dog - thing3 - false - 12执行yq '@csv' sample.yml输出:
cat,"thing1,thing2",true,3.40 dog,thing3,false,12执行yq '@tsv' sample.yml输出(字段以 Tab 分隔):
cat thing1,thing2 true 3.40 dog thing3 false 12@csv与@tsv同样是 0 缩进编码,且由encodeOperator负责去除结尾换行,因此适合直接重定向到.csv/.tsv文件。整文件级的-o=csv、-p=csv转换与表头行为细节可参考 csv-tsv 文档。
XML:属性前缀、内容名与缩进
XML 与其他格式的关键差异在于:XML 的属性和文本内容在 YAML 表示中需要约定标记。官方文档指出,XML 使用--xml-attribute-prefix与--xml-content-name两个 flag 来识别属性字段与内容字段;在 cmd/root.go 中可以看到它们分别绑定到ConfiguredXMLPreferences.AttributePrefix与ConfiguredXMLPreferences.ContentName(默认属性前缀为+@,内容名为+content)。
编码为多行 XML 字符串
给定sample.yml:
a: cool: foo: bar +@id: hi执行:
yq '.a | to_xml' sample.yml输出:
<cool id="hi"> <foo>bar</foo> </cool>+@id键被识别为cool元素的属性,渲染为id="hi"。
编码为单行 XML:@xml
yq '.a | @xml' sample.yml输出:
<cool id="hi"><foo>bar</foo></cool>@xml与to_xml的差别同 JSON 一致:前者固定 0 缩进,后者默认 2 缩进。
自定义缩进:to_xml(1)
yq '{"cat": .a | to_xml(1)}' sample.yml输出:
cat: | <cool id="hi"> <foo>bar</foo> </cool>解码 XML 字符串
给定sample.yml:
a: <foo>bar</foo>执行:
yq '.b = (.a | from_xml)' sample.yml输出:
a: <foo>bar</foo> b: foo: bar若你的 XML 使用自定义属性前缀(如--xml-attribute-prefix="+attr")或自定义内容字段名,from_xml/to_xml会遵循同样的全局偏好,保证编解码往返一致。更完整的 XML 处理(命名空间、CDATA 等)可参考 xml 使用文档。
Base64:RFC 4648 标准编码
官方文档明确了两点约束:Base64 采用 RFC 4648 使用 Go 标准库base64.StdEncoding,并在Init阶段自动去除首尾空白、补齐=填充(padLen := len(stripped) % 4),因此解码时即使输入缺少填充字符也能正确还原。
编码字符串为 Base64
给定sample.yml:
coolData: a special string执行:
yq '.coolData | @base64' sample.yml输出:
YSBzcGVjaWFsIHN0cmluZw==编码整个 YAML 文档为 Base64
先经@yaml把对象结构转成字符串,再交给@base64:
yq '@yaml | @base64' sample.yml给定a: apple输出:
YTogYXBwbGUK这是"把 YAML 文档打包进另一个 YAML 字段"的常用手法。
解码 Base64 字符串
解码结果假定为字符串。给定:
coolData: V29ya3Mgd2l0aCBVVEYtMTYg8J+Yig==执行:
yq '.coolData | @base64d' sample.yml输出:
Works with UTF-16 😊注意:这里演示的是 UTF-16 内容被先转成 UTF-8 字节再编码,解码后即为 UTF-8 的Works with UTF-16 😊。由于实现假定 UTF-8 文本,直接对二进制文件内容做 base64 往返不在其保证范围内。
解码并解析 Base64 中的 YAML
管道串联@base64d与from_yaml:
yq '.coolData |= (@base64d | from_yaml)' sample.yml给定coolData: YTogYXBwbGUK输出:
coolData: a: apple注意此处用的是|=而非=,因为目标是原地替换coolData字段的值。这与 operator_load.go 提供的load_base64等文件加载运算符思路一致,只是这里直接从字段值读取。
URI:百分号编码与解码
URI 编码用于把字符串安全地放进 URL。给定:
coolData: this has & special () characters *执行:
yq '.coolData | @uri' sample.yml输出:
this+has+%26+special+%28%29+characters+%2A注意空格被编码为+(表单风格),&、()、*被百分号编码。反向操作:
yq '@urid' sample.yml对内容this+has+%26+special+%28%29+characters+%2A解码,输出:
this has & special () characters *Shell:@sh 生成 Shell 友好字符串
@sh用于把字符串转成可直接放进sh/bash命令行的形式,这在生成脚本、拼接 CI 命令时非常实用。给定:
coolData: strings with spaces and a 'quote'执行:
yq '.coolData | @sh' sample.yml输出:
strings' with spaces and a '\'quote\'其原理见 encoder_sh.go:unsafeChars正则([^\w@%+=:,./-])判定字符是否安全,不安全的字符段用单引号块包裹,遇到引号则"退出单引号块 + 反斜杠转义 + 重新进入",从而保证生成的字符串能在 shell 中安全使用。从 format.go 可以看到ShFormat只有EncoderFactory而没有DecoderFactory,因此@sh没有对应的解码运算符;源码也提示非字符串节点会报错"please first pipe through another encoding operator to convert the value to a string"。
实战组合与注意事项
综合上述各格式,可以把 encode/decode 家族归纳为三种典型用法:
- 嵌入(Encode):
'.field = (.obj | to_json(0))'、'@yaml | @base64'——把对象序列化后存进另一个 YAML 字段,常用于配置模板、数据打包。 - 解析(Decode):
'.a |= @csvd'、'.b = (.a | from_xml)'——把字符串化内容还原为可继续用 yq 表达式查询/修改的对象。 - 往返更新(Roundtrip):
'.a |= (from_yaml | .foo = "cat" | to_yaml)'——解码、修改、再编码并原地写回;得益于decoded:变量机制,单行与多行形态会被自动保留。
实践中的几个关键点:
- indent 参数只作用于可传参的三个编码函数:
to_yaml(i)、to_json(i)、to_xml(i);而@yaml/@json/@xml速记分别固定为 2 / 0 / 0 缩进。@csv、@tsv、@base64、@uri、@sh无缩进概念。 - JSON 解码后接
style运算符:因为 JSON 是 YAML 子集,from_json会保留引号样式,... style=""可得到地道的 YAML。 - CSV/TSV 解码默认以首行为表头,且默认会对条目做 YAML/JSON 自动解析,可用
--csv-auto-parse=f关闭(见 csv-tsv 文档)。 - XML 的属性与内容约定由
--xml-attribute-prefix(默认+@)和--xml-content-name控制,跨编解码时必须保持一致。 - Base64 面向 UTF-8 文本而非任意二进制,且按 RFC 4648 标准实现,自动处理空白与
=填充。
这些运算符的完整行为均由仓库中的表格驱动测试覆盖(operator_encoder_decoder_test.go),官方文档 encode-decode.md 的每个示例都可直接复制运行验证。掌握这一族运算符后,无论你面对的是嵌在 YAML 里的 JSON 配置、字符串化的 properties 文件,还是需要 base64/URI 转义的动态内容,都能用一条 yq 表达式完成解析与重组。
【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考