一言以蔽之
YAML 是一种对人类友好的结构化数据写法,它摒弃了其近亲们的花括号和引号,换之以如同购物清单般整洁的缩进。
它解决了什么问题
起初,世界一片混沌。然后,配置文件诞生了。像 .ini 这样的早期格式虽然简单,但无法处理复杂的嵌套数据。接着 XML 出现了,它功能强大、结构清晰,但因为太过冗长、标签泛滥,读起来就像在用法言法语写的说明书组装宜家家具。人类讨厌写这玩意儿。
紧随其后的是 JSON (JavaScript Object Notation),这是一个巨大的进步。它轻量、能直接映射到大多数编程语言的数据结构,而且比 XML 顺眼多了。但对于那些需要人类大量编写和编辑的文件——比如 DevOps 脚本、应用设置和国际化文本——JSON 的语法仍然让人觉得繁琐。那些花括号、逗号和引号不仅是视觉噪音,还特别容易搞错。
于是,YAML 登场了。它的名字是一个递归缩写,完美地体现了它的精神:“YAML Ain't Markup Language”(YAML 不是一种标记语言)。它从一开始就是为了一位主要受众设计的:盯着屏幕的人类。它采用了与 JSON 相同的基本数据结构(键值对、列表和简单值),然后提出了一个问题:“要表示这些数据,我们到底需要多精简的语法?”
答案是缩进。通过使用空白来表示结构,YAML 创造出一种通常足够清晰、可以自我解释的格式。它就是为配置领域而生的,在那个世界里,清晰度和易编辑性远比面向高吞吐量 API 的机器优化需求更重要。
底层工作原理
YAML 的“魔法”不过是一套简单、一致的规则,用来将缩进的文本转换成结构化数据。它是 JSON 的一个超集,这意味着你通常可以把合法的 JSON 粘贴到 YAML 文件里,它也能正常工作。但它真正的威力来自于其原生的、极简的语法。
基本构成:标量、序列和映射
YAML 中的所有数据都归结为三样东西:
映射 (Mappings, 又称字典或对象): 这就是经典的
key: value键值对。键是字符串,而值可以是任何东西:另一个映射、一个序列,或者一个标量。# 一个简单的映射 character: "Bilbo Baggins" race: "Hobbit" age: 111序列 (Sequences, 又称列表或数组): 这是有序的项目列表。每个项目都由一个连字符和一个空格 (
-)表示。# 一个字符串序列 fellowship_members: - Frodo Baggins - Samwise Gamgee - Gandalf - Legolas - Gimli标量 (Scalars, 又称简单值): 这就是一个单一的值,比如字符串、数字或布尔值。YAML 在猜测类型方面相当智能。
123是数字,true是布尔值,而Hello world是字符串。你通常不需要引号,但如果你的字符串可能会被误解(例如"true"、"1.23"),就应该使用引号。
秘诀:缩进和空白
这是 YAML 中最重要的概念。这里没有花括号 {} 或方括号 [] 来表示嵌套,你只需要缩进。规则很简单:如果一行的缩进比它上面那行多,它就成为上面那行的子元素。
让我们把这些基本构件组合起来。这是一个角色的档案,里面包含一份物品清单。
# 一个嵌套结构
character:
name: "Gollum"
aliases:
- "Sméagol"
- "My Precious"
possessions:
- item: "The One Ring"
description: "A plain gold ring, surprisingly heavy."
- item: "A fish"
description: "Juicy and sweet!"
is_wretched: true
看看这个结构。name、aliases、possessions 和 is_wretched 都是 character 的属性,因为它们都缩进在 character 下面。aliases 序列是 character 映射中的一个值。possessions 序列包含了两个映射对象,每个对象都有 item 和 description。
缩进的量不重要,只要在同一个块内保持一致就行。两个空格是社区标准。但你必须用空格,不能用制表符(tab)。 使用制表符是让你陷入无尽的隐形痛苦的头号元凶。
高级技巧:锚点、别名和标签
YAML 有一些 JSON 所没有的“高玩”功能,旨在让你的文件保持 DRY (Don't Repeat Yourself - 不要重复自己)。
锚点 (
&) 和别名 (*): 如果你有一块需要重复使用的数据,可以用锚点 (&anchor_name) 给它起个名字,然后在其他地方用别名 (*anchor_name) 来引用它。# 用锚点定义一个默认用户配置 default_user: &default_user_profile theme: "dark" notifications: "enabled" permissions: "read-only" # 现在创建继承默认配置的特定用户 users: - name: "Alice" # 使用别名来引入默认配置 <<: *default_user_profile # 并覆盖一个特定的键 permissions: "admin" - name: "Bob" # Bob 使用标准配置 <<: *default_user_profile这里,
<<是一个特殊的合并键。Alice 和 Bob 都获得了默认配置,但 Alice 的permissions键被覆盖了。这在复杂的配置中简直是救星。标签 (
!!): YAML 通常会推断类型,但你也可以用标签来明确指定。这在避免歧义时很有用。例如,如果你想要字符串"12.0"而不是数字12.0。version: !!str 12.0 # 强制这是个字符串 not_a_boolean: !!str "no" # 强制这是个字符串
真实世界的故事
消失的流水线疑案
一位初级 DevOps 工程师,我们叫她 Chloe 吧,接到了一个任务:给公司的 CI/CD 流水线添加一个新的安全扫描。流水线是用一个 gitlab-ci.yml 文件定义的。她添加了新的作业,推送了代码,然后……什么也没发生。流水线跑了,但她的新扫描作业无影无踪。它没有失败,就是凭空消失了。Chloe 花了两个小时检查她的脚本语法、runner 配置和阶段定义。最后,她筋疲力尽地请一位资深工程师帮忙看看。资深开发者的眼睛在文件上扫了大约五秒钟,然后指向了一行。Chloe 在定义她的新作业时,用了三个空格缩进,而不是像文件其他地方那样用两个空格。YAML 解析器把它当作前一个作业的一个格式错误的子元素,而不是一个新的顶级作业,并悄悄地忽略了它。
教训: 在 YAML 中,空白即语法。一个错位的空格就能改变整个文件的含义。使用 linter 或能够可视化数据树的结构化编辑器,可以立即发现这类错误。
长成森林的配置文件
一家小型创业公司用一个 config.yml 文件管理他们的应用环境(开发、预发、生产)。一开始,这很简单。但随着他们增加了更多环境(prod-us、prod-eu、dev-feature-x),这个文件爆炸了。针对数据库 URL、API 密钥和功能开关的大块配置在每个环境中被复制粘贴,只有微小的改动。文件变成了一个 500 行的怪物,改一个共享的值,比如超时设置,需要在五个不同的地方查找和替换。一位刚从大公司来的新员工看到了这个情况,并引入了 YAML 锚点。他定义了一个 &default_config 块,包含了所有通用设置。然后,每个环境的配置只需简单地引用默认值 (<<: *default_config),并覆盖那少数几个不同的值。这个 500 行的文件缩减到了 100 行以下。
教训: 不要重复自己。如果你发现自己在 YAML 文件里大段地复制粘贴,是时候学习并使用锚点和别名了。
挪威问题
一位开发者正在开发一个功能,让用户可以从下拉菜单中选择他们的国家。国家代码列表存储在一个简单的 YAML 文件中:supported_countries: [ US, DE, UK, NO ]。在测试期间,来自挪威 (NO) 的用户抱怨他们无法注册。开发者花了几个小时调试代码,追踪变量,但找不到问题所在。NO 这个值从前端正确地传过来了。最后,他检查了从 YAML 文件加载的数据。他程序中的 supported_countries 数组是 ['US', 'DE', 'UK', false]。YAML 解析器遵循了某个旧版本的规范,将未加引号的 NO 解释为了表示“假”的布尔值。
教训: 如有疑问,给你的字符串加上引号。任何看起来像数字 ("1.0")、布尔值 ("yes", "no", "on", "off") 或特殊值的标量,都应该明确地用引号包起来,以避免解析时出现意外。
常见错误和陷阱
- 用制表符(tab)代替空格。 这是 YAML 的头号大罪。规范禁止使用制表符。因为它们是不可见的,所以可能导致令人抓狂、难以发现的解析错误。请配置你的编辑器,在处理 YAML 文件时使用空格。
- 缩进不一致。 如果一个列表项缩进了两个空格,而下一个缩进了四个,那你就有麻烦了。结构将被错误地解析。请保持缩进级别一致。
- 忘记给有歧义的字符串加引号。 “挪威问题”是个经典案例。像
Yes、No、true、false、On、Off这样的字符串会被解析为布尔值。带有前导零或特殊字符的数字也可能被错误解析。如有疑问,就用""引号把它包起来。 - 多行字符串的困惑。 忘记
|(字面样式,保留换行符)和>(折叠样式,将换行符转换为空格)之间的区别。这可能导致你精心格式化的文本块或 shell 脚本被弄得一团糟。 - 意外的
null值。 一个键的冒号后面什么都没有(key:)就是一个null值。这通常是意外删除造成的,如果你的代码没有检查null,可能会导致静默失败。
为什么你应该关注它
如果你在 2024 年写代码,你不可能绕开 YAML。它是无可争议的配置之王。
- DevOps & 基础设施即代码 (Infrastructure-as-Code): Kubernetes、Ansible、Docker Compose、GitHub Actions、AWS CloudFormation 以及无数其他工具都使用 YAML 作为其主要的定义语言。
- 应用配置: 许多框架(如 Symfony 和 Ruby on Rails)和应用程序使用 YAML 作为设置文件,因为它对开发者来说非常易于阅读和修改。
- 静态网站生成器: 像 Jekyll 和 Hugo 这样的工具使用 YAML 作为“frontmatter”来定义文章和页面的元数据。
了解 YAML 不仅仅是为了写配置文件,更是为了理解你所使用的系统的结构。能够发现一个细微的缩进错误,或者知道何时使用锚点,可能就是快速修复问题和浪费一天时间调试的区别。
深入探索
- YAML Spec 1.2.2: 官方的真理之源。它很密集,但却是最终的参考。
- Wikipedia: YAML: 对该语言的历史、特性和版本的一个很好的高层次概述。
- Learn YAML in Y minutes: 一个极好的单页速查表,带有实时示例,涵盖了你将需要的 80% 的内容。
- YAML Lint: 一个在线验证器,对于找到那些讨厌的语法错误和理解解析器“看到”了什么非常有价值。
- GitHub Docs: Workflow syntax for GitHub Actions: 一个完全用 YAML 定义的复杂系统的绝佳真实世界示例。研究它可以揭示许多常见的模式。