编程 locale 起效顺序与六个坑:从 locale-gen 到 LC_ALL=C

2026-10-15 01:37:05

locale 起效顺序与六个坑:从 locale-gen 到 LC_ALL=C

locale 是什么

Locale(区域设置/本地化环境)是 glibc 提供的一组语言与文化规则:消息语言、字符集、日期时间格式、货币、数字小数点与千分位、字符串排序等。程序通过 setlocale() 查询或设置当前 locale,locale.h 里以宏声明各种 category。

名称格式为 language[_TERRITORY][.CODESET][@modifier],例如 zh_CN.UTF-8、de_DE@euro、en_DK.UTF-8。language 用 ISO 639,territory 用 ISO 3166。

相关环境变量与优先级

  • LANG:默认区域设置,未设置的 LC_* 都继承它。
  • LC_*(LC_COLLATE / LC_CTYPE / LC_MESSAGES / LC_MONETARY / LC_NUMERIC / LC_TIME / LC_PAPER / LC_ADDRESS / LC_NAME / LC_TELEPHONE / LC_MEASUREMENT / LC_IDENTIFICATION):只覆盖某一类行为。
    • LC_COLLATE 影响 strcoll/strxfrm 与 sort、ls 的排序;
    • LC_CTYPE 影响字符分类与多字节处理;
    • LC_MESSAGES 影响 gettext 翻译消息与 rpmatch 的 yes/no 判断;
    • LC_NUMERIC 影响 printf/scanf 的小数点;
    • LC_TIME 影响 strftime 的日期时间;
    • LC_MONETARY 影响 strfmon 的货币格式。
  • LC_ALL:它不是普通环境变量,而是能被 setlocale 设置的宏;它覆盖 LANG 和所有 LC_*。也正因为如此,LC_ALL 不能写进 locale.conf,官方建议只在测试/排障时临时设置。
  • LANGUAGE:GNU gettext 专用于翻译语言的后备列表,冒号分隔,如 zh_CN:en_US:en。注意:若 LANG 或 LC_ALL 被设为 "C",LANGUAGE 会被忽略;很多程序把英文 locale 当作默认 C 而非 en_US,所以常见写法是 LANGUAGE=en_US:en:C:es_ES。

setlocale(category, "") 时的解析顺序见 locale(7):

  1. 若存在非空 LC_ALL,用它;
  2. 否则若该类别的同名环境变量非空,用它;
  3. 否则若 LANG 非空,用它;
  4. 都没有则为 "C"/"POSIX",LC_CTYPE 退化为 7 位 ASCII。

生成与启用

Debian/Arch 等需要先安装 locale 数据(locales 或 locales-all 包):

  • Arch:编辑 /etc/locale.gen 取消注释,执行 locale-gen。
  • Debian:dpkg-reconfigure locales;系统默认写在 /etc/default/locale;locales-all 是预编译数据,不创建 /etc/default/locale。
  • systemd:
localectl set-locale LANG=zh_CN.UTF-8
localectl list-locales
  • 查看已生成:locale -a、localedef --list-archive。
  • 优先级由 /etc/profile.d/locale.sh 定义;用户可在 ~/.config/locale.conf 覆盖。

常见坑与排查

  1. 容器/精简镜像没装 locale,perl 等运行时刷 Failed to set locale, defaulting to C。装 locales 并生成,或显式 LC_ALL=C 接受纯 ASCII 行为。
  2. 脚本输出随环境变化:sort/ls 在 LANG=en_US.UTF-8 下是字典序 A->a->B->b,在 LANG=C.UTF-8 下是 ASCII 二进制序 A->B->...->a->b。要在脚本里得到稳定可解析的输出,用 LC_ALL=C(或 LC_COLLATE=C)显式锁定,别依赖 SSH 环境。
  3. 数字小数点:欧陆 locale 用逗号,printf/scanf/atoi 混用会出错。涉及金额或 CSV 解析时锁 LC_NUMERIC=C。
  4. LC_ALL=C.UTF-8 与 LC_ALL=C 不同:前者不覆盖 LANGUAGE,后者覆盖。glibc bug 16621 / gettext bug 62815 有记录。
  5. LC_ALL 在启动文件里设置会隐藏其它 LC_* 的改动,官方建议不写进启动文件,只用 LANG + 个别 LC_* 修正。
  6. 死键、终端字符集与 locale 编码不一致时中文乱码;SSH 客户端未透传 LANG 时要在客户端 SendEnv、服务端 AcceptEnv 上配置。

建议

大多数场景只全局设 LANG=xx_YY.UTF-8,个别用 LC_* 修正,例如日志要英文就保持 LC_MESSAGES 为 en 或 C。排障时先跑 locale 看现状,再临时 LC_ALL=C 复现「纯 ASCII 行为」,判断问题是否由 locale 引起。

参考资料:man 7 locale、Arch Wiki Locale、Gentoo Localization Guide、Debian 国际化与本地化章节。

复制全文 生成海报 locale Linux i18n 环境变量 Shell 运维

推荐文章

程序员茄子在线接单