为什么 YAML 一直没有被淘汰:从 Clash 配置说开去

😤 每一个被 YAML 折磨过的人都想骂它

说实话,YAML 挨的骂可能比任何一种配置文件格式都多。缩进错了,整个应用崩掉;多了个空格,解析器直接报错不给任何面子。我猜每个第一次写 Clash 配置的人都至少被“YAML 格式错误”折磨过一次。我刚学代理那会儿,从网上复制了一段规则贴到配置文件里,运行日志直接爆红,查了半天发现是某一行前面多了一个 tab 而不是两个空格——肉眼根本看不出来。那种想砸键盘的冲动,我相信你也有过。

但神奇的是,这么多年过去了,JSON、TOML、HCL 一茬一茬地冒出来,YAML 不仅没死,反而牢牢占据着 DevOps、CI/CD、容器编排等关键领域,当然也包括我们每天都在用的 Clash Verge Rev 配置文件。它的生命力到底来自哪里?这篇文章我就想掰扯掰扯这个话题——以一个被 YAML 虐过无数次的普通用户的视角。

📜 YAML 不是一夜之间冒出来的

YAML 全称“YAML Ain't Markup Language”,创始人 Clark Evans 在 2001 年提出这个想法的时候,目标是创建一个“人类可读性优先”的数据序列化格式。它最初的灵感来自 XML 的复杂冗长,以及 JSON 的前身——JavaScript 对象字面量。YAML 用缩进代替了 XML 的尖括号,用 :- 代替了 JSON 的 {}[]。最早的应用场景其实是 Ruby 社区的配置管理和数据传输,后来逐渐被 Python、Ansible 等大规模采用。

YAML 设计上有个很特殊的追求:它支持复杂的引用(锚点和别名)、多行字符串(字面量和折叠两种模式)、类型标签,以及至今仍有争议的“Norway problem”(布尔值识别)。这些特性有的让 YAML 变得无比强大,有的成了它的黑历史。不过,对一个代理客户端来说,绝大多数时间我们只用到它最基础的字典和列表功能,而这些恰恰是 YAML 做得最自然的部分。

🤷 为什么没直接用 JSON?

可能有人会问,Clash 配置本质上就是一堆节点、规则和策略组,完全可以用 JSON 表达啊,而且 JSON 没有缩进问题,解析器也更通用。这话没错,但你要是看过一个包含几百条规则的 JSON 文件,就不会这么想了。

JSON 的几个致命伤让它不适合做“人经常手动编辑”的配置:

  • 不支持注释。这是最大的硬伤。我可以在 YAML 里写一堆 # 以下规则用于 ChatGPT 之类的说明,下次打开还能看懂当初为什么这么配。JSON 里你只能靠命名去猜,或者写个 "__comment__": "解释" 这种自欺欺人的字段。
  • 括号和逗号太啰嗦。每一个对象都要 {},每一个数组都要 [],最后一项还不能加逗号。手工维护的时候,每次新增或删除一项,都要小心翼翼地检查逗号。YAML 的 - 列表语法天然避免了这个问题。
  • 多行字符串表达困难。JSON 只能 "第一行\n第二行",整段复制粘贴时简直痛苦。YAML 的 |> 可以让多行内容保持原样或自动折叠,这对于写配置文件中的 ws-path 或者 payload 之类字段尤其方便。
  • 视觉噪音多。人眼天生更适合看缩进和换行来表达层级关系,而不是数括号。不信你把 Clash 的完整配置转换成 JSON 看一眼,绝对头晕。

TOML 其实是个不错的折中方案,但它的生态远不如 YAML 广泛,而且 Clash 生态从一开始就选定了 YAML,历史惯性已经形成。如果你对配置文件的详细写法还不熟悉,可以先翻翻 配置教程,里面详细解释了 YAML 里各个字段的写法。

💡 小提醒: 你可以用 yamllint 或在线工具来检查配置文件是否有格式问题,省得启动客户端时看到一长串报错。

🎯 在 Clash 配置里,YAML 其实帮了大忙

具体到 Clash Verge Rev 的日常使用,YAML 的优势会体现得更明显。首先是 规则的可读性。一个典型的规则段是这样的:

# 广告拦截 DOMAIN-SUFFIX,doubleclick.net,REJECT # ChatGPT 专用 DOMAIN-SUFFIX,openai.com,🚀 ChatGPT # 国内直连 GEOIP,CN,DIRECT

这种“规则类型、匹配目标、动作”的格式用 YAML 的列表写出来非常直观。如果用 JSON,就是 ["DOMAIN-SUFFIX,doubleclick.net,REJECT", "DOMAIN-SUFFIX,openai.com,🚀 ChatGPT"],可读性大打折扣。而且,YAML 允许你用 RULE-SET 配合 Rule Provider 把数千条规则放到远程文件里,主配置文件只保留几行引用,这极大地降低了手动维护的复杂度。

其次是 策略组和节点列表。在 YAML 里,你可以清晰地看到每个策略组的类型、包含的节点、健康检查的 URL。特别是当你需要在节点之间切换时,这个结构就像一张表格,一目了然。

最后是 注释带来的协作能力。如果你跟朋友共享配置,或者在 GitHub 上分享自己的配置,注释可以解释每一个规则块的意图。这种能力在 导入订阅 的场景下也很重要——当你从机场拿到一份默认配置,想加上自己的自定义规则,可以直接在原文件上注释标注。

🧩 多行字符串、锚点和别名的进阶玩法

YAML 里有些高级功能,虽然不常用,但用好了能极大提升配置维护效率。比如 多行字符串。有些节点需要配置 TLS 的证书内容或者自定义的 Payload,如果用 JSON 那种一行写法根本没法看。YAML 提供了两种模式:

# 字面量模式(保留换行) ws-headers: Host: | example.com another-header: value # 折叠模式(换行转空格) description: > 这是一段很长的描述文字, 它会自动把换行替换成空格, 适合写说明。

另一个是 锚点和别名,它可以在一个配置里定义重复内容的引用。比如你有多个节点只是服务器地址和密码不同,但加密方式和混淆设置完全一样,就可以这样写:

default-settings: &base type: ss cipher: aes-256-gcm plugin: obfs plugin-opts: mode: tls proxies: - name: "节点A" <<: *base server: server-a.com password: pass-a - name: "节点B" <<: *base server: server-b.com password: pass-b

这在管理大量节点时特别实用,减少了重复输入,也避免了因为手误改错参数。虽然 Clash 的内核 Mihomo 并不完全支持 YAML 的所有高级特性(比如合并键可能会被忽略),但类似技巧在本地配置维护时仍然可以帮到你。

⚠️ 注意: 不是所有 YAML 解析器都支持复杂的锚点和标签,Clash 的配置引擎通常只支持最基础的字典和列表。使用高级特性前最好先验证。

🕳️ 那些年我们踩过的 YAML 坑

YAML 的坑可谓千奇百怪,下面这些是我亲身遇到以及从社区 常见问题 里整理出来的:

  • Tab vs 空格。YAML 只认空格,不认 Tab。有些编辑器默认会插入 Tab,导致文件看起来对齐,实际解析失败。VS Code 里可以开启“显示空白字符”,把 Tab 替换成两个空格。
  • 布尔值陷阱。YAML 会自动把 yesnoonoff 等解析为布尔值,而不是字符串。如果你的节点名称叫 no,可能会被解析成 false,让你完全摸不着头脑。解决办法是加引号,比如 "no"
  • 多行字符串的缩进。使用 |> 时,后面的内容需要有一个缩进层级,而且要保证每一行缩进一致。有时候从一个地方复制过来的文本看起来对齐了,实际混入了不可见字符,就会报错。
  • 冒号后面必须跟空格。写 key:value 会被当成字符串,而正确的写法是 key: value。这个错误在复制域名端口信息时容易出现。

这些坑让很多人对 YAML 深恶痛绝,但熟悉之后,你会形成一种肌肉记忆。比如我现在写配置,已经习惯性地先敲一个冒号再加一个空格,而且每写几行就保存并用客户端检查一下。

🔮 YAML 会一直存在下去吗

很多人预测 YAML 会被 TOML、HCL 或者某种新的格式取代,但现实是,YAML 在配置管理和基础设施即代码领域的地位已经根深蒂固。Kubernetes 的清单文件、Ansible 的 Playbook、Docker Compose 的编排文件,全部用 YAML。这些工具形成了强大的网络效应,只要它们还在,YAML 就死不了。

对于 Clash Verge Rev 来说,YAML 也是短期内不可能更换的基础组件。Mihomo 内核对 YAML 的支持已经非常成熟,而且社区沉淀了大量的教程和模板。即使有一天出现更好的格式,兼容现有配置的成本也太高了。所以,与其盼着它退休,不如学着跟它和平共处。如果你想进一步了解 YAML 在代理工具中的具体应用,可以参考 Rule 模式详解DNS 配置 以及 Rule Provider 实战,这些文章里都有大量 YAML 示例供你参考。

📖 继续阅读