引言
「配置该用什么格式」看似是小事,实则是每个项目都要做、做错了很难改的决策。INI 简单到 30 行就能写完解析器,却在嵌套和类型上寸步难行;TOML 有严格规范和完整类型系统,成了 Cargo、pyproject 的默认;HCL 引入了块(block)和表达式,把配置变成了可编程的基础设施描述语言。
本文横向对比这三者的语法、类型、嵌套、注释与解析陷阱,并把 JSON、YAML 拉进来做参照,最后给一张按场景的选型表和迁移建议。
对照阅读:JSON 与 YAML 处理 、语义化版本与依赖解析 。基础设施落地见 DevOps 专题 。
1. 配置格式的三个维度
评价一种配置格式,核心看三个维度:
| 维度 | 含义 | INI | TOML | HCL |
|---|---|---|---|---|
| 表达力 | 能表达多深的嵌套、数组、类型 | 单层 | 任意嵌套 | 任意嵌套 + 表达式 |
| 规范性 | 是否有唯一权威规范 | 无 | 有(v1.0.0) | 有(语法 + 求值) |
| 可编程性 | 能否引用、计算、条件 | 无 | 无 | 有 |
选型时还要叠加:可读性(人写得多还是机器生成)、工具生态(有无 Schema、格式化器、Linter)、语言支持(你的技术栈是否有一等公民库)。
2. INI:简单与混乱并存
2.1 基本语法
INI 只有三种元素:节(section)、键值对、注释。
; 分号注释
# 井号注释也常见
[database]
host = localhost
port = 5432
name = myapp
[server]
workers = 4
debug = true
2.2 方言问题
INI 没有官方规范,各解析器行为不一:
| 行为 | Python configparser | Windows API | PHP parse_ini_file |
|---|---|---|---|
| 值默认类型 | 全是字符串 | 字符串 | 会推断 int/bool |
| 冒号作分隔符 | 支持 | 支持 | 支持 |
| 重复键 | 报错 | 后者覆盖 | 后者覆盖 |
| 大小写 | 键不敏感 | 键不敏感 | 键敏感 |
| 多行值 | 缩进续行 | 不支持 | 不支持 |
这意味着同一个 .ini 在不同语言里可能解析出不同结果——跨语言共享配置时,这是硬伤。
2.3 嵌套与数组的表达困境
INI 只能靠「点号分节」或「数字后缀」模拟嵌套,既丑又易错:
[server]
listen.0 = 0.0.0.0:80
listen.1 = 0.0.0.0:443
db.host = localhost
db.port = 5432
解析后需要自己把扁平键重新折叠成树。数组、深层嵌套、混合类型几乎无法优雅表达。
2.4 什么时候仍该用 INI
- 配置极浅(一层节 + 键值),如
.gitconfig、.editorconfig、桌面应用设置。 - 需要被 C、老脚本、Windows API 直接读取。
- 追求「任何人 5 秒看懂」。
3. TOML:为配置而生的规范
3.1 设计目标
TOML(Tom’s Obvious Minimal Language)的定位是「无歧义、易读、映射到哈希表」。它有正式规范(v1.0.0),类型明确,且解析结果可直接映射到大多数语言的原生字典。
3.2 完整类型系统
# 字符串(四种)
basic = "hello\nworld"
literal = 'C:\Users\no\escape'
multiline = """
多行
字符串
"""
multiline_literal = '''
原样多行,不转义 \n
'''
# 数字
int_dec = 42
int_hex = 0xDEADBEEF
int_oct = 0o755
int_bin = 0b1010
int_underscore = 1_000_000
float = 3.14
float_exp = 5e22
inf = inf
nan = nan
# 布尔
enabled = true
# 日期时间(一等公民,这是 TOML 的独特优势)
date = 1979-05-27
time = 07:32:00
datetime = 1979-05-27T07:32:00Z
local_dt = 1979-05-27T07:32:00
# 数组(可混合类型,但实践中应同质)
ports = [8000, 8001, 8002]
matrix = [[1, 2], [3, 4]]
# 内联表
point = { x = 1, y = 2 }
日期时间作为原生类型是 TOML 相比 INI/YAML 的显著优势——YAML 的时间解析依赖实现,JSON 根本没有时间类型。
3.3 表与表数组
[server]
host = "0.0.0.0"
port = 8080
[server.tls] # 嵌套表
cert = "/etc/cert.pem"
key = "/etc/key.pem"
[[servers]] # 表数组(数组元素是表)
name = "alpha"
weight = 1
[[servers]]
name = "beta"
weight = 2
[[name]] 是 TOML 的精华:它用自然语法表达「对象数组」,等价于 JSON 的 {"servers": [{"name": "alpha"}, {"name": "beta"}]}。
3.4 必须注意的解析规则
- 表定义不能重复:
[a]出现两次是错误,除非用[[a]]。 - 键的顺序敏感:
[a.b]之后再写[a]会报错(因为a已被隐式创建为表)。 - 内联表不可再扩展:
point = {x=1}之后不能写[point.y]。 - 点号键:
a.b.c = 1等价于[a.b]下的c,但要小心与显式表混用。
# 合法
[a]
x = 1
[a.b]
y = 2
# 非法:a 已作为表被定义,不能再定义内联
# a = { x = 1 }
# [a.b]
3.5 工具生态
# 格式化(保持一致的排序与缩进)
taplo fmt config.toml
# Schema 校验(TOML Schema 或 JSON Schema)
taplo check --schema schema.toml config.toml
主流语言都有成熟库:Rust toml、Python tomllib(3.11+ 标准库,只读)、Go BurntSushi/toml、Java tomlj。
4. HCL:可编程的基础设施配置
4.1 块与表达式
HCL(HashiCorp Configuration Language)在键值基础上引入了块(block)和表达式(expression),服务 Terraform、Packer、Consul 等工具。
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = var.instance_type # 引用变量
count = 3
tags = {
Name = "web-${count.index}" # 字符串插值
Env = var.environment
}
}
variable "instance_type" {
type = string
default = "t3.micro"
}
4.2 表达力:引用、函数、条件
HCL 的核心竞争力是「配置即计算」:
locals {
is_prod = var.environment == "prod"
worker_cnt = local.is_prod ? 10 : 2
subnets = [for s in var.subnets : cidrsubnet(s, 8, 1)]
}
resource "aws_autoscaling_group" "app" {
desired_capacity = local.worker_cnt
min_size = local.is_prod ? 5 : 1
}
这是 INI/TOML 完全做不到的:for 表达式、条件运算符、内置函数(cidrsubnet、lookup、merge)。
4.3 两种语法:原生与 JSON
HCL 有两种等价语法,.tf(原生,人类写)和 .tf.json(JSON,机器生成):
{
"resource": {
"aws_instance": {
"web": { "ami": "ami-12345678", "instance_type": "t3.micro" }
}
}
}
工具通常先读原生语法,允许生成器输出 JSON 语法。
4.4 陷阱
- 类型转换是隐式的:
"3"与3在多数上下文可互换,但某些函数严格。 - 变量未定义会报错,不像 YAML 那样静默为空。
- 块类型由宿主工具定义,HCL 语法本身不认识
resource、variable——这些是 Terraform 的 schema。所以 HCL 的「可读性」高度依赖宿主的文档。
5. JSON / YAML 作为配置的对照
| 特性 | JSON | YAML | TOML | INI | HCL |
|---|---|---|---|---|---|
| 注释 | 无 | 有 | 有 | 有 | 有 |
| 类型 | 有限 | 丰富(隐式) | 丰富(显式) | 无 | 丰富 |
| 嵌套 | 好 | 好 | 好 | 差 | 好 |
| 表达式 | 无 | 无 | 无 | 无 | 有 |
| 多行字符串 | 差 | 好 | 好 | 差 | 好 |
| 人类友好 | 中 | 好(陷阱多) | 好 | 好 | 中 |
| 机器友好 | 极好 | 中 | 好 | 中 | 中 |
YAML 的「隐式类型」是最大陷阱:NO、on、yes 在某些解析器里是布尔,1.0 可能变浮点,1:30 可能变时间。配置文件里出现 country: NO(挪威)被解析成 false 是著名事故。
JSON 无注释、无多行字符串,只适合机器生成,不适合人类维护。
5.1 JSON 的两种改良方言
为缓解 JSON 的短板,出现了 JSON5 与 JSONC(JSON with Comments):
{
// 允许注释
"name": "app",
"retries": 3, // 允许尾随逗号
"endpoint": "http://x",
}
JSON5 还支持单引号字符串、无引号键名、十六进制数字。它们被 VS Code(settings.json、tsconfig.json)等工具采纳,但不是标准 JSON,跨工具传递时要确认解析器支持。把 JSON5 当标准 JSON 发给严格解析器,是另一类常见事故。
6. 类型系统与解析陷阱
6.1 类型推断 vs 显式类型
- INI/PHP:
debug = true→ 布尔;port = 8080→ 整数。看似方便,实则不可控。 - TOML:
debug = true是布尔,debug = "true"是字符串,显式且唯一。 - HCL:有类型转换但受 Schema 约束。
6.2 环境变量插值与密钥
纯配置文件不该硬编码密钥。常见做法:
# TOML 无原生插值,靠读取方处理
[database]
password = "${DB_PASSWORD}"
# HCL 用函数读环境变量
provider "aws" {
region = "us-east-1"
# access_key 从环境变量 AWS_ACCESS_KEY_ID 隐式读取
}
TOML 本身不支持插值,需要应用层在加载后替换 ${VAR}。YAML 也不支持,但许多工具(Docker Compose、Kubernetes)在解析前做一层模板替换。
6.3 Schema 校验
| 格式 | 校验方案 |
|---|---|
| TOML | TOML Schema / JSON Schema(经 taplo) |
| YAML | JSON Schema(如 Kubernetes CRD、IDE 插件) |
| HCL | 宿主工具自带 schema(Terraform 的 block 定义) |
| JSON | JSON Schema(最成熟) |
| INI | 基本无标准方案 |
7. 选型决策与迁移
7.1 按场景选
| 场景 | 推荐 | 理由 |
|---|---|---|
| 语言包管理(Cargo/pyproject) | TOML | 生态已成事实标准 |
| 应用运行配置 | TOML / YAML | 类型清晰,可读性好 |
| 基础设施即代码 | HCL | 需要表达式与引用 |
| CI/CD 流水线 | YAML | 工具链生态锁定 |
| 桌面/老系统设置 | INI | 兼容性与简单性 |
| 机器生成的配置 | JSON | 无歧义、库最全 |
| 极简、跨语言共享 | TOML | 有规范、类型明确 |
7.2 INI → TOML 迁移示例
; 旧
[database]
host = localhost
port = 5432
# 新:把值改成显式类型
[database]
host = "localhost"
port = 5432
迁移要点:给字符串补引号、把布尔从小写词改成 true/false、把扁平的点号键折叠成嵌套表。
7.3 一条实践建议
配置格式一旦选定,改动成本远高于想象——它会被 CI、部署脚本、文档、IDE 插件、团队肌肉记忆同时引用。选型时优先考虑「生态锁定」而非「语法偏好」:你的工具链默认支持哪种,就用哪种。Kubernetes 用 YAML、Terraform 用 HCL、Cargo 用 TOML,都不是因为格式本身最优,而是因为生态。
8. 小结
INI 胜在极简,输在无规范、无嵌套、无类型;TOML 用严格规范补齐了这些短板,成为应用配置的现代默认;HCL 引入块与表达式,把配置升级为可编程描述,专为基础设施而生。选型的核心不是「哪种语法更好看」,而是「哪种格式与我的工具生态和团队能力最匹配」。更深一层的解析原理(词法、递归下降、锚点、别名炸弹)可回到 JSON 与 YAML 处理 。基础设施侧的配置落地,参见 DevOps 专题 与 Kubernetes 专题 ,Web 服务器侧的具体写法见 nginx 核心配置 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。