配置格式对比:TOML、INI 与 HCL

深入对比三种主流配置格式:INI 的极简与方言混乱、TOML 的强类型规范与表数组设计、HCL 的可编程块与表达式求值,并对照 JSON/YAML 作为配置的优劣。覆盖语法细节、类型系统、嵌套与数组表达、注释与多行字符串、解析陷阱、Schema 校验、环境变量插值、以及按场景(应用配置/基础设施/CI)的选型与迁移策略。

引言

「配置该用什么格式」看似是小事,实则是每个项目都要做、做错了很难改的决策。INI 简单到 30 行就能写完解析器,却在嵌套和类型上寸步难行;TOML 有严格规范和完整类型系统,成了 Cargo、pyproject 的默认;HCL 引入了块(block)和表达式,把配置变成了可编程的基础设施描述语言。

本文横向对比这三者的语法、类型、嵌套、注释与解析陷阱,并把 JSON、YAML 拉进来做参照,最后给一张按场景的选型表和迁移建议。

对照阅读:JSON 与 YAML 处理 、语义化版本与依赖解析 。基础设施落地见 DevOps 专题 。

1. 配置格式的三个维度

评价一种配置格式,核心看三个维度:

维度含义INITOMLHCL
表达力能表达多深的嵌套、数组、类型单层任意嵌套任意嵌套 + 表达式
规范性是否有唯一权威规范无有(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 configparserWindows APIPHP 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 作为配置的对照

特性JSONYAMLTOMLINIHCL
注释无有有有有
类型有限丰富(隐式)丰富(显式)无丰富
嵌套好好好差好
表达式无无无无有
多行字符串差好好差好
人类友好中好(陷阱多)好好中
机器友好极好中好中中

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 校验

格式校验方案
TOMLTOML Schema / JSON Schema(经 taplo)
YAMLJSON Schema(如 Kubernetes CRD、IDE 插件)
HCL宿主工具自带 schema(Terraform 的 block 定义)
JSONJSON 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 核心配置 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「others」更多文章

  1. HTTP 缓存与条件请求
  2. CSV/TSV 解析陷阱
  3. 模板引擎原理与选型