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):
- 若存在非空
LC_ALL,用它; - 否则若该类别的同名环境变量非空,用它;
- 否则若
LANG非空,用它; - 都没有则为
"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覆盖。
常见坑与排查
- 容器/精简镜像没装 locale,perl 等运行时刷
Failed to set locale, defaulting to C。装locales并生成,或显式LC_ALL=C接受纯 ASCII 行为。 - 脚本输出随环境变化:
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 环境。 - 数字小数点:欧陆 locale 用逗号,
printf/scanf/atoi混用会出错。涉及金额或 CSV 解析时锁LC_NUMERIC=C。 LC_ALL=C.UTF-8与LC_ALL=C不同:前者不覆盖LANGUAGE,后者覆盖。glibc bug 16621 / gettext bug 62815 有记录。LC_ALL在启动文件里设置会隐藏其它LC_*的改动,官方建议不写进启动文件,只用LANG+ 个别LC_*修正。- 死键、终端字符集与 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 国际化与本地化章节。