go-ini 是稳定可靠的 Go INI 解析库,需正确处理路径、BOM、节存在性检查、嵌套节命名、类型转换(优先用 Must 方法)、并发安全及写入原子性等问题。

go-ini 库读取 INI 文件的基本用法
直接用 ini.Load() 加载文件,它会自动处理节(section)、键值对和注释。别手写解析器——go-ini 是目前最稳定、被广泛验证的 Go INI 解析库,不是玩具项目。
常见错误是传错路径或忽略返回的 error:文件不存在、权限不足、编码含 BOM 都会导致 ini.Load() 返回非 nil 错误,但很多人只 check err == nil 就继续往下跑,结果 cfg.Section("").Key("xxx") panic。
- 确保路径是绝对路径,或基于可执行文件所在目录拼接(用
filepath.Abs()或os.Executable()配合filepath.Dir()) - 加载后先调用
cfg.Section("xxx")检查节是否存在,再调用.Key("keyname"),避免空指针 - 默认不支持 UTF-8 with BOM,如果编辑器保存带 BOM,需先用
bytes.TrimPrefix()去掉
读取嵌套节和子节(如 [database.mysql])
go-ini 原生支持点号分隔的嵌套节名,但不是靠递归解析,而是把 [database.mysql] 当作一个独立节名字符串处理。它不会自动挂载到 database 下——这点和 Python 的 configparser 行为不同,容易误解。
使用场景比如微服务中按环境+组件拆分配置:[redis.dev]、[redis.prod],你想动态选节名,就得拼字符串再调用 cfg.Section()。
立即学习“go语言免费学习笔记(深入)”;
- 节名中的点号只是命名约定,
cfg.Section("database.mysql")和cfg.Section("database/mysql")是两个完全无关的节 - 没有内置的“继承”或“fallback”机制,
[database]和[database.mysql]的键互不影响 - 若需模拟继承(比如共用
host),得手动用cfg.Section("database").Key("host").String()fallback
类型转换和默认值设置(String() / MustInt64() 等)
所有 Key().Xxx() 方法都带默认值兜底逻辑,但行为有差异:String() 返回空字符串,MustInt64(0) 才返回 0;不带 Must 前缀的方法在解析失败时返回零值,也不报错——这很危险,尤其对数字和布尔类型。
典型坑是写 cfg.Section("").Key("timeout").Int64(),配置里写的是 timeout = 30s(带单位),结果返回 0,程序静默用错值。
- 优先用
MustInt64()、MustBool()等带Must前缀的方法,并传入合理默认值 - 对可能含单位的字段(如
timeout = 5s),别依赖go-ini自动转,应读成字符串后用time.ParseDuration()处理 -
Key().String()不做 trim,空格会保留;需要干净字符串就自己strings.TrimSpace()
写入 INI 文件和并发安全问题
go-ini 支持写回(cfg.SaveTo()),但不保证原子写入,也没有锁机制。多个 goroutine 同时调用 SaveTo() 可能导致文件损坏或丢失数据。
更关键的是:cfg 实例本身不是线程安全的。即使只读,如果一边在 cfg.Section().Key().SetValue(),另一边在 Key().String(),也可能 panic。
- 写操作必须加外部锁(如
sync.Mutex),且建议整个 SaveTo 过程独占 - 读多写少场景下,可考虑每次修改后生成新
*ini.File,而不是复用旧实例 - 生产环境慎用运行时改写配置文件,优先走热重载 + 信号通知,或换用 etcd/Consul 等外部配置中心
INI 格式看着简单,但节嵌套语义、类型弱校验、BOM 处理、并发写入这几处,实际项目里踩过的坑比想象中多。尤其是从其他语言迁移过来的人,容易按旧习惯假设“节名带点就自动分层”,结果调试半天才发现是名字没对上。










