
yq -oshell 实战把 YAML 配置扁平化为可 source 的 Shell 变量文件【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yqyq 除了输出 YAML/JSON 等常规格式外还支持一个相对小众但实用性很强的输出格式-oshell。该格式可以把任意层级的 YAML 映射/数组结构递归展平为KEYvalue形式的 shell 变量赋值语句变量名合法化、值自动加引号输出可以直接被source或用作.env风格的环境变量文件。读完本篇你将掌握yq -oshell的完整用法含--shell-key-separator自定义分隔符、键名清洗与引号转义的底层规则以及空值、标量文档等边界情况的处理行为并能在源码层面encoder_shellvariables.go验证这些行为的来龙去脉。输出格式注册shell 是“只编码、不解码”的格式在 yq 的格式注册表中shell 格式注册为正式名shell别名s和sh见 format.govar ShellVariablesFormat Format{shell, []string{s, sh}, func() Encoder { return NewShellVariablesEncoder() }, nil, // 没有 DecoderFactory不能作为输入格式 }两个关键事实它是纯输出格式。第二个短名列表为空、DecoderFactory为nil意味着yq不能把 shell 变量文件读回来作为输入只能单向导出它同时注册在默认格式列表中format.go因此-oshell、-os、-osh三种写法等价。另外注意仓库中还有一个ShFormatencoder_sh.go它负责表达式里|sh运算符对单个值做 shell 转义unsafeChars regexp.MustCompile([^\w%:,./-])按字符懒打开/关闭单引号块。它与本文的主角shellVariablesEncoder是两回事前者处理单个字符串值后者把整棵文档树展平为变量集合。编码器还带有构建标签encoder_shellvariables.go 顶部是//go:build !yq_noshell而 no_shellvariables.go//go:build yq_noshell会在裁剪构建下返回nil编码器——即 shell 输出支持可以通过 build tag 整体移除。快速上手嵌套结构与数组的展平官方文档的第一个示例给定 [examples 风格] 的sample.yml# comment name: Mike Wazowski eyes: color: turquoise number: 1 friends: - James P. Sullivan - Celia Mae执行yq -oshell sample.yml输出nameMike Wazowski eyes_colorturquoise eyes_number1 friends_0James P. Sullivan friends_1Celia Mae行为要点注释被丢弃。doEncode的源码注释明确写着 Note this drops all commentsencoder_shellvariables.go测试TestShellVariablesEncoderStripComments验证了a: Alice # comment得到aAlice嵌套映射用_拼接为路径数组用下标friends数组的两个元素变成friends_0、friends_1值按“需要时才加引号”策略处理Mike Wazowski含空格被单引号包裹而turquoise、1等纯字母数字/下划线的值不加引号。展平的核心是doEncode的递归实现encoder_shellvariables.goMappingNode逐对取键值并appendPath拼接路径SequenceNode按索引拼接路径AliasNode则跟随别名指向的节点继续编码CanHandleAliases()返回false指它不会输出别名节点本身但递归时会展开为别名目标。进入Encode之前还会先调用mapKeysToStrings(node)把所有键转成字符串再处理。自定义键分隔符--shell-key-separator当原始键本身就含下划线时比如my_app、db_config默认的_拼接会产生歧义my_app__db_config__host无法反推出层级边界。--shell-key-separator允许指定任意分隔符官方示例my_app: db_config: host: localhost port: 5432yq -oshell --shell-key-separator__ sample.ymlmy_app__db_config__hostlocalhost my_app__db_config__port5432该 flag 在 root.go 中注册直接绑定到yqlib.ConfiguredShellVariablesPreferences.KeySeparator默认值为_见 shellvariables.gofunc NewDefaultShellVariablesPreferences() ShellVariablesPreferences { return ShellVariablesPreferences{ KeySeparator: _, UnwrapScalar: false, } }单元测试覆盖了多种分隔符场景encoder_shellvariables_test.go输入 YAML分隔符输出a:\n b: Lewis\n c: Carroll__a__bLewis/a__cCarrollmy_app:\n db_config:\n host: localhost__my_app__db_config__hostlocalhosta: [{n: Alice}, {n: Bob}]__a__0__nAlice/a__1__nBoba:\n b: valueXaXbvalue单字符分隔符同样支持非法变量名的键三步清洗规则shell 变量名必须匹配[a-zA-Z_][a-zA-Z0-9_]*。YAML 键几乎可以包含任意字符因此appendPathencoder_shellvariables.go按以下步骤对每个键做“合法化”Unicode NFKD 兼容性分解golang.org/x/text/unicode/norm把带重音的字母拆成基础字母 重音符号拆解连字把上标数字换成普通数字丢弃所有非 ASCII 字符和 ASCII 控制字符码点 32或 126上一步留下的重音符号在此被丢弃保留基础字母其余非字母数字字符一律替换为_。源码实现为一次strings.Mapkey : strings.Map(func(r rune) rune { if isAlphaNumericOrUnderscore(r) { return r } else if r 32 || 126 r { return -1 // 丢弃 } return _ // 替换 }, norm.NFKD.String(fmt.Sprintf(%v, rawKey)))另外只有根键若清洗后不以[a-zA-Z_]开头即原键以数字开头会在前面补一个_——因为$_是特殊 shell 变量不能赋值而空变量名value在 source 时会报错源码注释见 encoder_shellvariables.go。官方文档第二个示例ascii__symbols: replaced with _ ascii_ _controls: dropped (this example uses \t) nonascii_א_characters: dropped effort_expeñded_tò_preserve_accented_latin_letters: moderate (via unicode NFKD)yq -oshell sample.ymlascii___symbolsreplaced with _ ascii__controlsdropped (this example uses \t) nonascii__charactersdropped effort_expended_to_preserve_accented_latin_lettersmoderate (via unicode NFKD)可以看到_被替换成_占位、\t和希伯来字符被整体丢弃留下双下划线、ñ/ò被 NFKD 拆开后重音被丢弃、基础字母得以保留。单元测试还覆盖了几种典型键encoder_shellvariables_test.go输入键输出变量名说明be\allbell转义控制字符被丢弃b-e llb_e_l_l连字符/空格/等号替换为_béllbell非 ASCII 重音被丢弃1a_1a数字开头的根键补__key_key下划线开头的根键原样保留root:\n _childroot__child子键的下划线与分隔符叠加源码注释特别强调这并非 1:1 映射不同原始键可能折叠到同一个变量名导出前应注意键名冲突。值引号策略与单引号转义值的加引号逻辑在quoteValueencoder_shellvariables.go中func quoteValue(value string) string { needsQuoting : false for _, r : range value { if !isAlphaNumericOrUnderscore(r) { needsQuoting true break } } if needsQuoting { return strings.ReplaceAll(value, , \\) } return value }规则是只要值里出现任一非[a-zA-Z0-9_]字符就用单引号整体包裹值内部的单引号编码为闭单引号 双引号包裹的单引号 开单引号。官方示例name: Miles OBrienyq -oshell sample.ymlnameMiles OBrien对应测试TestShellVariablesEncoderQuotesQuotinga: Lewis Carrolls Alice编码为aLewis Carroll\\s Alice。边界情况空值、空容器与纯标量文档官方文档第三部分规定了空值的语义“空值编码为空变量但空数组和空映射被跳过”。empty: value: array: [] map: {}yq -oshell sample.ymlempty_value解释这些行为需要回到doEncode的递归结构SequenceNode/MappingNode只是遍历子节点空容器没有任何子节点可写因此自然不产生输出测试TestShellVariablesEncoderEmptyArray/EmptyMap期望输出为字符串而标量空值是ScalarNode分支路径照常拼接、值为空串故输出empty_value测试TestShellVariablesEncoderEmptyValue期望empty。还有一个容易忽略的边界如果整个文档就是一个标量没有映射结构路径为空——不能输出somevaluesource 会报错也不能用变量名_$_是特殊变量。源码为此选择了兜底键valueencoder_shellvariables.gononemptyPath : path if path { // We cant assign an empty variable somevalue because that would error out if sourced in a shell, // nor can we use _ as a variable name ($_ is a special shell variable that cant be assigned) nonemptyPath value }测试TestShellVariablesEncoderScalarNode验证输入some string输出valuesome string。此外遇到不属于ScalarNode/SequenceNode/MappingNode/AliasNode的节点类型编码器会直接报错unsupported node tag这是该格式对输入结构的硬约束。参数与行为速查参数 / 行为取值 / 默认说明依据输出格式名shell别名s、sh-oshell触发本编码器无解码器不能用作输入format.go--shell-key-separator默认_键路径分隔符可为任意字符串root.go、shellvariables.goUnwrapScalar默认false是否对值去引号直出由 CLI 全局 unwrap 选项驱动写入ConfiguredShellVariablesPreferencesutils.go且不像 YAML/Properties 格式那样对 shell 输出自动开启utils.go注释丢弃shell 变量文件不含注释encoder_shellvariables.go空值 / 空容器key/ 跳过空标量保留空数组与空映射不输出测试 encoder_shellvariables_test.go别名节点展开为别名目标AliasNode分支递归node.Aliasencoder_shellvariables.go典型应用场景-oshell的自然用法是把 YAML 配置变成可直接被 shellsource的脚本或环境变量注入# 生成可 source 的环境变量文件 yq -oshell config.yml config.env # 在 Docker entrypoint 中 source /config.env echo $my_app__db_config__host结合 yq 的表达式能力还可以先筛选再导出例如只导出某个子树yq -oshell .my_app config.yml需要注意的适用前提与限制输出只能单向生成yq 不支持把 shell 变量文件读回格式无解码器键名合法化不可逆不同键可能映射到同一变量名重要配置建议先用--shell-key-separator区分或预先整理键名输出面向 POSIX 风格 shellbusybox ash 等的变量命名习惯含特殊字符的值通过单引号 转义保证source安全顶层为纯标量文档时会以value为兜底键多文档输入时每个文档独立编码文档分隔符输出为空PrintDocumentSeparator无操作。源码导读从命令到编码器的调用链整条链路可以概括为CLI 层root.go 注册--shell-key-separator并绑定全局偏好对象utils.go 的configureEncoder把 indent、unwrap 等选项写入各格式偏好包括ConfiguredShellVariablesPreferences.UnwrapScalar格式解析FormatFromString(outputFormat)按正式名/别名匹配到ShellVariablesFormatformat.go 的MatchesName编码层NewShellVariablesEncoder()读取ConfiguredShellVariablesPreferences构造shellVariablesEncoderencoder_shellvariables.goEncode先mapKeysToStrings再由doEncode递归展平appendPath负责 NFKD 键清洗quoteValue负责值的引号。行为验证集中在 encoder_shellvariables_test.go覆盖非引号/引号/单引号转义、注释剥离、映射展平、数组下标、非可打印键、数字开头根键、空值与空容器、纯标量文档、四种自定义分隔符及 unwrap 模式等场景与本文前述各节一一对应。如果你要修改或扩展该格式例如增加export前缀输出以该测试文件为回归基准是最稳妥的做法。【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考