Keep learning, keep living...

0%

关于应用程序的locale设置

近期开发的C++程序遇到一个关于locale的问题。程序在通过SSH直接登录到root用户后,运行程序会崩溃,而通过其他用户SSH登录后,再切换到root用户后,程序则运行正常。

1
2
terminate called after throwing an instance of 'std::runtime_error'
what(): locale::facet::_S_create_c_locale name not valid

根据locale文档中的描述:

1
2
3
4
5
6
7
8
9
10
11
std::locale::locale(const char * __s)	[explicit]

Named locale constructor.

Constructs a copy of the named C library locale.

Parameters:
s Name of the locale to construct.

Exceptions:
std::runtime_error if s is null or an undefined locale.

可以得知,指定的locale不存在。

Google上查到一般解决方案是设置环境变量LC_ALL=C可以解决。

下面来看看问题出现的原因和这样解决的原因。

localeUNIX/Linux上指的是一系列环境变量,它根据用户所使用的语言、国家或者地区、字符集等来设置系统的语言环境。应用程序可以根据这个设置来展示不同的语言信息。

locale把所涉及到的使用习惯的各个方面分成12个大类, 每类都有独立的环境变量来设置。

可以使用locale命令来展示相应的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@default ~]# locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

上边结果显示的不只有12个独立的LC_*环境变量,还有LC_ALLLANG两个环境变量。

它们的生效优先级是:

1
LC_ALL > LC_* > LANG

也就是, LC_ALL是强制设定,设置它之后可以覆盖其他变量内容。LANG是默认值,其他值没有设置时,使用它做为默认值。

我们开头提到的问题原因看上去是指定的locale不存在,而在不同的SSH登录环境下表现却有所不同,因而可能是不同环境下默认的locale不同。经过调研发现:

直接使用root用户SSH登录后,执行locale命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@default ~]# locale
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
LANG=zh_CN.UTF-8
LC_CTYPE="zh_CN.UTF-8"
LC_NUMERIC="zh_CN.UTF-8"
LC_TIME="zh_CN.UTF-8"
LC_COLLATE="zh_CN.UTF-8"
LC_MONETARY="zh_CN.UTF-8"
LC_MESSAGES="zh_CN.UTF-8"
LC_PAPER="zh_CN.UTF-8"
LC_NAME="zh_CN.UTF-8"
LC_ADDRESS="zh_CN.UTF-8"
LC_TELEPHONE="zh_CN.UTF-8"
LC_MEASUREMENT="zh_CN.UTF-8"
LC_IDENTIFICATION="zh_CN.UTF-8"
LC_ALL=

默认的localezh_CN.UTF-8

而使用其他用户SSH登录再切换为root之后,执行locale:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@default ~]# locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

默认的localeen_US.UTF-8

而通过locale查找,这台服务器上确实没有zh_CN.UTF-8的语言包:

1
2
3
4
5
6
7
[root@default ~]# locale -a |grep zh_CN
[root@default ~]#
[root@default ~]# locale -a |grep en_US
en_US
en_US.iso88591
en_US.iso885915
en_US.utf8

因而当启动我们应用程序的bashlocalezh_CN.UTF-8时,程序抛出了异常,而localeen_US.UTF-8时则正常运行。

那么这两种情况下locale的差异来源于哪里呢?经过调研发现,默认情况下,SSH连接会设置目标服务的环境变量,可以在配置文件:/etc/ssh/ssh_config中进行调整:

1
2
3
4
5
6
7
8
9
10
11
Host *
GSSAPIAuthentication yes
# If this option is set to yes then remote X11 clients will have full access
# to the original X11 display. As virtually no X11 client supports the untrusted
# mode correctly we set this to yes.
ForwardX11Trusted yes
# Send locale-related environment variables
SendEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
SendEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
SendEnv LC_IDENTIFICATION LC_ALL LANGUAGE
SendEnv XMODIFIERS

由于我本地MAC电脑的localezh_CN.UTF-8, 因而使用root建立SSH连接后,目标服务器上该SSH连接所运行的bashlocale被设置为zh_CN.UTF-8, 而通过其他用户切换到root, 则使用了root的默认locale:en_US.UTF-8

应用程序不能依赖所有执行环境都能正确配置好locale, 因而我们需要在应用程序中自行指定好locale。也就是我们文章开头所说的,设置环境变量LC_ALLC

locale: C是一个特殊的locale, 其他的locale是用于向人类展示信息的,而C表示computer, 它是用于向计算机展示信息的。它的字符集是单字节的ASCII。它在POSIX兼容的系统上都存在,因而我们的程序设定localeC后,彻底摆脱了运行环境的影响,可以解决文章开头的问题。

参考: