LC_ALL引发的血案

问题描述

我们的svn服务器要从虚拟机升级到docker,迁移完成后,发现本地用TortoiseSVN提交代码后,收到的通知邮件里面,commit信息中的中文无法正常显示,类似这样:

1
@modify ?\233?\170?\140?\232?\175?\129svn?\229?\141?\135?\231?\186?\167?\229?\144?\142?\239?\188?\140?\233?\170?\140?\232?\175?\129?\228?\184?\173?\230?\150?\135?\228?\185?\177?\231?\160?\129?\233?\151?\174?\233?\162?\152

解决过程

我们怀疑和docker的LANG环境变量有关系。但是由于自己对这块并不熟悉,只是凭感觉,尝试进行了几次设置:

1
2
3
4
# 第一次尝试
export LANG=zh_CN.UTF-8
# 第二次尝试
export LANG=zh_CN.GBK

两次修改后,发现邮件收到的中文都仍然是乱码的(注意:这里其实我犯了一个错,我是直接用root用户在命令行执行的环境变量设置,并未真正修改http进程的环境变量)。

这样修改一下,然后提交代码,通过收取邮件来验证的方式,效率太低了。考虑到我们代码检查如果不通过,也会输出提示信息,因此我修改了下代码文件,故意增加了一些无法通过的代码,然后通过TortoiseSVN界面提交,发现输出了一些报错信息:

1
2
svn: warning: cannot set LC_CTYPE locale
svn: warning: environment variable LANG is zh_CN.UTF-8

网上遇到这个问题的人很多,都是说只要做如下设置就能解决:

1
export LC_ALL=C

然后我修改了/etc/profile文件,添加了这个设置,然后source /etc/profile,发现还是没解决这个问题。

接着我猜测可能http进程的环境变量未生效,然后就直接在svn的hook插件脚本pre-commit里面,加入了上面的export LC_ALL=C这一行。

Warning:这里给后面埋了一个很大的坑!

正巧这个时候,另外一位同事,给docker安装了glic-common,然后我重新提交之前有问题的那个代码文件,发现

svn: warning: cannot set LC_CTYPE locale这个报错没有了,其实这个时候并不确定到底是我加了export LC_ALL=C解决的,还是同事安装了glic-common解决的。

然后此时,发现提交的时候,TortoiseSVN界面展示的代码检查信息,中文也变成unicode了。

接下去,我们尝试了各种安装中文语言包、设置LANG等等,都没有解决这个问题,折腾了好几个小时。

最后,GX怀疑是我们的hook脚本有问题,让我查看这个脚本,发现在脚本顶部有我刚加的export LC_ALL=C以及以前我添加的export LANG=zh_CN.UTF-8,怀疑是这个没生效。

注意:GX很确定是LANG导致的问题,而且很确定LANG应该设置为zh_CN.UTF-8,这是他解决问题的方向,也是最终解决了问题的关键。

而我根本没有明确的思路,全靠猜。

脚本内容如下:

1
2
3
4
5
6
#!/bin/sh
#这一行是前面我调试时加的,也是引发问题的关键
export LC_ALL=C
#(这是以前的代码注释,忽略)之所以要拆分成pre-commit和pre-commit-xx.py,是因为在python中设置语言有问题
export LANG=zh_CN.UTF-8
/usr/bin/python3 /data/svn/check/pre-commit-web.py $1 $2

然后我们将脚本里面获取的LANG记录到日志文件中,发现没有问题。

然后将svn提交时的事务信息,即上面的$1和$2记录到日志中,通过命令行的方式进行调试。

我之前都是通过TortoiseSVN界面调试问题,效率太低了。

发现命令行执行脚本,输出的提示信息也是unicode,不是中文。

然后猜测和顶部的这2个LANG设置有关,导致python脚本没有获取到LANG=zh_CN.UTF-8的语言设置。

然后郭昕调整了脚本,改成这样:

1
2
3
4
#!/bin/sh
#export LC_ALL=C
#export LANG=zh_CN.UTF-8
LANG=zh_CN.UTF-8 /usr/bin/python3 /data/svn/check/pre-commit-web.py $1 $2

再次执行脚本,发现中文可以正常显示了!

至此问题得以解决。

原因分析

LC_ALL和LANG是有优先级的:

LC_ALL > LC_* >LANG

我在程序中如下设置:

1
2
3
#!/bin/sh
export LC_ALL=C
export LANG=zh_CN.UTF-8

就导致了其实最终生效的是LC_ALL=C,而这代表着去除所有本地化的设置,就无法显示中文信息了。

这个我在自己机器上没重现,感觉这个原因似乎不对,尚待验证。

知识点

locale中,”POSIX”是”C”的别名。所以当我们新安装完一个系统时,默认的locale就是C或POSIX。


LC_ALL=C是一个宏,如果该值设置了,则该值会覆盖所有LC_*的设置值。注意,LANG的值不受该宏影响


locale的命名规则为<语言>_<地区>.<字符集编码>,如zh_CN.UTF-8,zh代表中文,CN代表大陆地区,UTF-8表示字符集


参考文章:https://yintech.iteye.com/blog/397380