什么是 YAML?一种人性化的数据语言
YAML 浅显易懂
YAML 的全称是 'YAML Ain't Markup Language'(YAML 不是标记语言)—— 这是一个递归的俏皮话,直接告诉你它不是什么。它不像 HTML 那样用于标记文档。相反,YAML 是一种数据序列化格式,旨在让人无需在另一个标签页打开手册也能轻松阅读和编辑。它于 2001 年首次出现,由 Clark Evans、Ingy döt Net 和 Oren Ben-Kiki 创建。从那时起,它就成为了 Kubernetes、Ansible、GitHub Actions、Docker Compose 和 Ruby on Rails 等重要工具的默认配置语言。 YAML 的核心是使用缩进来表示数据结构,例如键值对和列表。你在这里看不到尖括号或花括号。一个简单的 Docker Compose 文件清晰地展示了这一点: version: '3.9' services: web: image: nginx:latest ports: - '80:80' 将其与等效的 JSON 进行比较,其吸引力显而易见。没有逗号,键上没有引号,也没有不匹配的闭合花括号。代价是缩进变得至关重要。如果只错一个空格,文件可能无法解析,或者更糟的是,默默地意味着完全不同的东西。这种易读性和严格结构之间的张力定义了 YAML 的使用体验。熟悉它,是熟练使用这种格式的关键。
YAML 如何构建数据:三大基石
每个 YAML 文档都由相同的基本部分组成:标量(scalars)、序列(sequences)和映射(mappings)。 标量只是一个单一值,例如字符串、数字、布尔值或 null。YAML 在推断类型方面很聪明,这在大多数情况下很有帮助,但有时也会适得其反。在较旧的 YAML 1.1 标准中,字符串 'yes' 被解析为布尔值 `true`,PyYAML 和其他一些旧工具仍在使用此标准。然而,YAML 1.2 正确地将其视为普通字符串。这个版本差异导致了实际的生产 bug,所以你绝对需要知道你的工具使用的是哪个解析器。 序列只是一个有序列表。你用一个前导短划线和一个空格来编写它: fruits: - apple - banana - mango 映射是一组键值对,这是你在配置文件中最常看到的。你可以将映射嵌套到任何深度,其结构完全由一致的缩进定义。YAML 规范对此有明确规定:你必须使用空格,绝不能使用制表符(tab)。 YAML 还提供了强大的功能来减少重复。锚点(&)和别名(*)允许你定义一次数据块,并在其他地方重复使用。这在 CI/CD 流水线中非常有用,多个作业可能共享完全相同的环境变量块。对于长字符串,YAML 有两种块样式:字面块标量(|)精确保留换行符,而折叠块标量(>)则将它们折叠成空格。这对于你想要在文件中保持可读性,同时又不给命令本身添加实际换行符的长 shell 命令来说是完美的。
YAML 的实际应用场景
YAML 在基础设施和开发者工具领域蓬勃发展。数据不会说谎。截至 2024 年,GitHub Actions 每天处理超过 1 亿次工作流运行,每一次都由项目 .github/workflows 目录中的 .yml 文件驱动。Kubernetes 作为大多数云原生应用程序的引擎,依赖 YAML 来定义每一个资源:Deployments、Services、ConfigMaps,应有尽有。一个典型的微服务应用程序很容易积累数百个 YAML 文件。 Ansible 是一个 IT 自动化工具,被超过 25,000 家组织使用(根据 Red Hat 数据),它使用 YAML 来编写其所有的 playbook。Ansible playbook 中的一个标准任务看起来像这样: - name: Install nginx ansible.builtin.package: name: nginx state: present 除了 DevOps 领域,你还会在像 Jekyll 这样的静态网站生成器中找到 YAML,Jekyll 使用 YAML front matter 在 Markdown 文件中存储元数据。Hoppscotch 和 Insomnia 等 API 测试工具使用它进行环境配置。甚至数据科学流水线也使用它;DVC(Data Version Control)等工具在 YAML 文件中跟踪实验参数。 你不太会看到 YAML 的一个地方是网络服务之间的数据交换。REST API 几乎普遍使用 JSON 作为请求和响应体。为什么?因为 JSON 解析器内置于每个浏览器中,而且该格式的严格性不留任何歧义空间。这是关键区别:YAML 适用于人类在磁盘上编辑文件;JSON 适用于机器通过网络传输数据。记住这个简单规则将避免在选择哪种格式时产生很多困惑。
YAML vs. JSON vs. TOML:选择正确的格式
在选择配置格式时,你通常会在 YAML、JSON 和 TOML 之间进行选择。它们各有鲜明的特点。 JSON (JavaScript Object Notation) 是最严格的。每个字符串都必须加引号,每个列表和对象都必须显式闭合,并且它不支持注释。最后一点是希望注释配置的开发者们主要感到沮丧的原因。但 JSON 的优势在于其严谨、明确的解析;任何两个符合规范的解析器都会从相同的输入生成相同的数据结构。对于典型配置,其文件大小通常与 YAML 相当。 TOML (Tom's Obvious, Minimal Language) 的创建是为了解决 JSON 缺乏注释和 YAML 棘手的空白规则问题。它使用 INI 风格的语法,并已被 Rust 的 Cargo 包管理器和 Python 的 pyproject.toml 标准采用。TOML 对于扁平或浅层嵌套的配置非常出色,但当你需要表示深度嵌套数据时,它会变得笨拙和冗长。 那么 YAML 适合在什么地方呢?当你的配置具有显著的嵌套深度、可以使用锚点和别名消除重复,或者非技术人员需要编辑文件时,YAML 显然是赢家。当你的团队不断与缩进错误作斗争,或者类型推断产生意外(例如臭名昭著的挪威问题,其中国家代码 'NO' 被解析为布尔值 `false`)时,它就是错误的选择。 如果你需要在格式之间转换,CocoConvert 提供可靠的 YAML 到 JSON 和 JSON 到 YAML 转换。然而,它不支持 TOML 作为输出格式。如果你需要从 YAML 转换到 TOML,你将不得不求助于像 `yq` 这样的命令行工具。最好提前了解这一点。
常见的 YAML 错误及如何避免
YAML 错误最常见的单一来源是不一致的缩进。任何花了一小时调试 CI 流水线却只发现一个空格放错位置的人都深知这种痛苦。与 Python 接受任何一致的缩进宽度不同,YAML 要求同一级别的兄弟键具有完全相同的缩进。在一个文件中混用两空格和四空格缩进会导致解析错误,或者更糟的是,默默地重构你的数据。唯一安全的工作方式是配置你的编辑器,将制表符转换为空格,并强制使用一致的缩进大小。两个空格是 Kubernetes 和 GitHub Actions 的事实标准。 未加引号的特殊字符是另一个陷阱。冒号后跟一个空格是键值分隔符,因此像 'http://example.com:8080' 这样的字符串必须加引号。忘记加引号,你就会得到一个解析错误。同样,以 `{`、`[` 或 `%` 开头的值需要加引号,因为它们在 YAML 语法中具有特殊含义。 然后是类型强制转换带来的意外。我们已经提到过 'no' 如何变成 `false`。但你知道带有前导零的数字可以被解析为八进制整数吗?值 0755,一个常见的 Unix 文件权限,除非你加引号,否则会变成十进制的 493。日期是另一个雷区;不加引号的 2024-01-01 会变成日期对象而不是字符串,这可能会破坏那些期望字符串的工具。 最好的防御是在提交 YAML 之前对其进行 lint 检查。`yamllint` 是一个必不可少的命令行工具,可以捕获缩进错误、尾随空格和其他常见问题。你的 CI 流水线绝对应该包含一个 `yamllint` 步骤。对于快速的一次性检查,将你的文件粘贴到 CocoConvert 的 YAML 到 JSON 转换器中是一个很好的健全性测试。如果生成的 JSON 结构与你预期的不符,那么你的 YAML 就有问题。
转换 YAML 文件:CocoConvert 能做什么
CocoConvert 为两种最常见的转换需求提供了工具:YAML 到 JSON 和 JSON 到 YAML。流程很简单:粘贴你的内容或上传一个 .yaml 文件,选择目标格式,然后下载结果。转换器准确地保留了你数据的嵌套结构。它还能正确处理多文档 YAML 文件(文档之间用 --- 分隔),将每个文档转换为一个大型数组中的独立 JSON 对象。 将 YAML 转换为 JSON 时,输出会以标准的两个空格缩进格式化,使其易读并与几乎所有 JSON 工具兼容。如果你需要一个紧凑的单行 JSON 字符串——例如用于环境变量或减小负载大小——结果页面上提供了压缩选项。 从 JSON 转换到 YAML 时,转换器使用两个空格缩进,并为单个文档省略文档起始标记 (---)。如果输入包含多个 JSON 对象,每个对象都会成为一个由 `---` 标记分隔的独立 YAML 文档。至关重要的是,它会自动引用那些可能被误解为布尔值、null 或数字的字符串值,所以你不必担心像 'true' 或 '1.0' 这样的字符串会引起问题。 让我们直接谈谈局限性。CocoConvert 在转换过程中不保留 YAML 注释。这不是工具的缺陷;注释不属于 YAML 的正式数据模型,因此它们会被解析器剥离。锚点和别名也会被解析,这意味着最终输出将包含重复值而不是引用。最后,非常大的文件(超过 10 MB)在免费层级可能会超时。对于那些大型任务,像 `yq` 这样的命令行工具是更好的选择。
何时使用 YAML,何时退避三舍
当满足以下几个条件时,YAML 就是合适的工具。文件将由人类阅读和编辑,数据具有有意义的嵌套结构,并且周围的生态系统已经在使用 YAML。Kubernetes manifests、CI/CD 流水线和 Ansible playbook 是这方面的典范。在这些场景中尝试使用 JSON 只会徒增麻烦,没有任何实际好处。 相反,当文件只由机器处理时,YAML 是错误的选择。对于机器到机器的通信,请使用 JSON 或更高效的二进制格式,如 MessagePack 或 Protocol Buffers。如果你的团队持续与缩进错误作斗争,并且缺乏使用 linter 的规范,那么它也是一个糟糕的选择。在这种情况下,TOML 更简单的语法,甚至是预处理过的 JSON 文件,都将导致更少的生产事故。 如果你在 Python 项目中使用 YAML,有一个你必须了解的关键安全风险。PyYAML 的默认 `yaml.load()` 函数可以执行嵌入在 YAML 文件中的任意代码。如果你正在解析来自不受信任来源的 YAML,你必须始终使用 `yaml.safe_load()`。这不是一个理论上的漏洞;它已在真实的供应链攻击中被利用。 这种格式已经伴随我们二十多年了,并且不会消失。YAML 1.2 规范修复了 1.1 版本中大部分烦人的类型强制转换问题,现在已得到 PyYAML 6.0+、js-yaml 4.x 和 Go 的 yaml.v3 等现代解析器的广泛支持。我最强烈的建议是:如果你正在从事一个严重依赖 YAML 的项目,迁移到符合 1.2 规范的解析器是你所能做的最具影响力的升级。它将消除一整类细微的 bug,而你无需更改一行配置。