yq 编码与解码运算符完全指南:从 to_json、from_yaml 到 @base64 的嵌入式格式处理实战
2026/9/14 17:56:23 网站建设 项目流程

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(对象 → 字符串)
YAMLfrom_yaml/@yamldto_yaml(i)/@yaml
JSONfrom_json/@jsondto_json(i)/@json
Propertiesfrom_props/@propsdto_props/@props
CSVfrom_csv/@csvdto_csv/@csv
TSVfrom_tsv/@tsvdto_tsv/@tsv
XMLfrom_xml/@xmldto_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 编码,而@json0 缩进的 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)会复制当前全局偏好(如ConfiguredYamlPreferencesConfiguredJSONPreferences),再覆盖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,而ShFormatDecoderFactorynil,这就是 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,与configureEncoderprefs.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 使用NewPropertiesEncoderNewPropertiesDecoder,配合 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.AttributePrefixConfiguredXMLPreferences.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>

@xmlto_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

管道串联@base64dfrom_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 家族归纳为三种典型用法:

  1. 嵌入(Encode)'.field = (.obj | to_json(0))''@yaml | @base64'——把对象序列化后存进另一个 YAML 字段,常用于配置模板、数据打包。
  2. 解析(Decode)'.a |= @csvd''.b = (.a | from_xml)'——把字符串化内容还原为可继续用 yq 表达式查询/修改的对象。
  3. 往返更新(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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询