首页 > 代码库 > 解决Linux终端乱码的两则例子

解决Linux终端乱码的两则例子

现象描述

我们先来说一下出现乱码的原因。

例子

先举个实际的例子,我们一般通过ssh远程到服务器上进行操作。当在终端上执行一些有输出的任务时,就遇到乱码了。

比如,我登陆上oracle数据库服务器上,查看oracle RAC的状态:

技术分享

比如上面的例子,除了英文字母外其它的都成了乱码了。 当然这个与运行什么程序没有什么关系,你可以试一下系统自带的命令,当参数错误时也会也现乱码。

技术分享

当我们在网上找问题的解决方法时,有让你修改配置文件的,有让你修改环境变量的,有让你换个客户端的,还有让你装语言包的。

有这么多答案供你先择,但都没有告诉你原因。 在给出答案之前我所先分析一下原因,为什么让我改环境变量,而不是其它的,它代表了什么?

当然为了不刨根问底,我设置一下范围,只关心系统层面,语言实现和架构层面我们就不涉及了。

原因

在Linux里面有两个概念与这个问题有关:国际化(相对应的是本地化)、编码

国际化-i18n-internationalization

在程序设计层面,输出有乱码的程序都是好程序,至少它考虑到了它的用户可能不是本国人,可能有不懂英文、德文的人在使用。

在不同的语言环境下,程序的输出会有不同。与之相关的细节则是环境变量LANG,系统命令locale等。

编码

编码是数据(文本)在计算机的内部表示。在数据存储(文件)或传输过程中会以某种规则进行编码(编码规范:UTF-8、GBK等)

题外话

可以这样理解,文本文件都是有编码的,这个概念在Windows中被弱化了。在中文版的Windows中的txt文件都是以gb2312编码的。找一个可以选择编码模式的编辑器比如“宇宙第一IDE"VSCODE,如果以utf-8打开gb2312编码的txt文件,就会显示乱码。

乱码

终于到关键点了,程序输出的乱码本质上和文件乱码是一回事。

文件乱码

以编码格式”A“写入文件,以编码格式"B“解析文件内容并显示

终端乱码

程序以系统要求(LANG)的编码格式"A"输出文本,输出的内容被”终端“程序以自己的编码格式”B"解析并显示。

”终端“程序的编码格式比较好理解,我们放几个截图就解释了。Linux系统内的约定比较不好理解,稍后我们再来看LANG, env, 以及locale.

Windows下的终端程序输出的编码格式

Windows自带的cmd程序

技术分享

 

Windows版本的Git Shell程序

技术分享

 

Linux下的终端程序输出的编码格式

Linux下的终端程序gnome-terminal

技术分享

通过菜单Terminal->Preferences->Profiles->Edit->Compatibility->Encoding进入相应配置界面

除了windows自带的cmd之外,其它终端都可以更改当前程序输出的编码。

Linux系统的编码是肿么个设定呢?

简单地说它是通过环境变量LANG来设定的,系统中的所有程序(包括gnome-termial)在启动时都会读取这个值来设定当前的程序菜单、界面、输出等编码格式。

能不能显示可能的确与字体包有关,比如redhat下的rpm包fonts-chinese-3.02-12.el5。它里面主要是一些字体文件,这些在windows里也能找到。

[root@newhis1 ~]# rpm -ql fonts-chinese
/usr/share/fonts/chinese/TrueType/fonts.cache-1
/usr/share/fonts/chinese/TrueType/fonts.dir
/usr/share/fonts/chinese/TrueType/fonts.scale
/usr/share/fonts/chinese/TrueType/ukai.ttf
/usr/share/fonts/chinese/TrueType/uming.ttf
/usr/share/fonts/chinese/fonts.cache-1
/usr/share/fonts/chinese/misc
/usr/share/fonts/chinese/misc/fonts.alias
/usr/share/fonts/chinese/misc/fonts.cache-1
/usr/share/fonts/chinese/misc/fonts.dir
/usr/share/fonts/chinese/misc/fonts.scale
/usr/share/fonts/chinese/misc/taipei16.pcf.gz
/usr/share/fonts/chinese/misc/taipei20.pcf.gz
/usr/share/fonts/chinese/misc/taipei24.pcf.gz
/usr/share/fonts/chinese/misc/vga12x24.pcf.gz
/usr/share/fonts/zh_TW
/usr/share/fonts/zh_TW/TrueType
/usr/share/fonts/zh_TW/TrueType/bsmi00lp.ttf

 我们假设这些包都装过了,也就是说在装系统时的“语言支持”里面选过中文了。

Linux支持的语言编码,通过locale命令可以查看。我们只关注英文和中文,所以过滤了一下。

[root@newhis1 ~]# locale -a|grep ‘^[z|e][h|n]‘|grep \\.|grep -v iso
en_AU.utf8
en_BW.utf8
en_CA.utf8
en_DK.utf8
en_GB.utf8
en_HK.utf8
en_IE.utf8
en_IN.utf8
en_NZ.utf8
en_PH.utf8
en_SG.utf8
en_US.utf8
en_ZA.utf8
en_ZW.utf8
zh_CN.gb18030
zh_CN.gb2312
zh_CN.gbk
zh_CN.utf8
zh_HK.big5hkscs
zh_HK.utf8
zh_SG.gb2312
zh_SG.gbk
zh_SG.utf8
zh_TW.big5
zh_TW.euctw
zh_TW.utf8

环境变量LANG的内容的格式为: <语言>_<地域>.<字符集>

环境变量LANGUAGE的格式为:<语言>_<地域>

还有命令locale输出的LC_开头的环境变量,它们会对程序的输出和界面产生响应。

细节可以参考网文:locale的设定中LANG、LC_ALL、LANGUAGE环境变量的区别

 为了防止链接损坏打不开,我抄一小段:

locale把按照所涉及到的文化传统的各个方面分成12个大类,这12个大类分别是: 
1、语言符号及其分类(LC_CTYPE) 
2、数字(LC_NUMERIC) 
3、比较和排序习惯(LC_COLLATE) 
4、时间显示格式(LC_TIME) 
5、货币单位(LC_MONETARY) 
6、信息主要是提示信息,错误信息,状态信息,标题,标签,按钮和菜单等(LC_MESSAGES) 
7、姓名书写方式(LC_NAME) 
8、地址书写方式(LC_ADDRESS) 
9、电话号码书写方式(LC_TELEPHONE) 
10、度量衡表达方式 (LC_MEASUREMENT) 
11、默认纸张尺寸大小(LC_PAPER) 
12、对locale自身包含信息的概述(LC_IDENTIFICATION)。 
Locale是软件在运行时的语言环境, 它包括语言(Language), 地域 (Territory) 和字符集(Codeset)。一个locale的书写格式为: 语言[_地域[.字符集]]。完全的locale表达方式是 [语言[_地域][.字符集] [@修正值]。zh_CN.GB2312=中文_中华人民共和国+国标2312字符集。 

locale的设定: 
LC_ALL和LANG优先级的关系:LC_ALL > LC_* >LANG 
1、如果需要一个纯中文的系统的话,设定LC_ALL= zh_CN.XXXX,或者LANG=zh_CN.XXXX都可以。 
2、如果只想要一个可以输入中文的环境,而保持菜单、标题,系统信息等等为英文界面,那么只需要设定 LC_CTYPE=zh_CN.XXXX,LANG=en_US.XXXX就可以了。 
3、假如什么也不做的话,也就是LC_ALL,LANG和LC_*均不指定特定值的话,系统将采用POSIX作为lcoale,也就是C locale。 
LANG和LANGUAGE的区别: 
LANG - Specifies the default locale for all unset locale variables 
LANGUAGE - Most programs use this for the language of its interface 
LANGUAGE是设置应用程序的界面语言。而LANG是优先级很低的一个变量,它指定所有与locale有关的变量的默认值 

 

结论

当环境变量LANG,  LANGUAGE, 和LC_开头的变量的设定与terminal程序的设定不一致或字符集不包含时就会出现乱码。

前面铺垫了那么多,当然不会就甩出一句A!=B就结束了,说好的两则例子呢?

我们分别以Linux下的gnome-terminal和Windows下的Git-shell为例演示一下上面的命令行报错的示例

Linux gnome-terminal

我们先确认一下当前终端使用的编码,在菜单Terminal->Preferences->Profiles->Edit->Compatibility->Encoding下。我们要演示正常的和不正常的。我们演示大家都能看懂的编码,以英文、中文简体、中文繁体及乱码为例来演示。

先查看一下terminal的编码选择的字符集:

技术分享

 

ssh在传输过程中可能会带入一些变量,可以打开verbose模式查看细节。

bash$  ssh -l root 192.168.10.85 -v
debug1: Sending environment.
debug1: Sending env LC_IDENTIFICATION = en_US.UTF-8
debug1: Sending env LC_TIME = en_US.UTF-8
debug1: Sending env LC_NUMERIC = en_US.UTF-8
debug1: Sending env LC_PAPER = en_US.UTF-8
debug1: Sending env LC_MEASUREMENT = en_US.UTF-8
debug1: Sending env LC_ADDRESS = en_US.UTF-8
debug1: Sending env LC_MONETARY = en_US.UTF-8
debug1: Sending env LANG = en_US.UTF-8
debug1: Sending env LC_NAME = en_US.UTF-8
debug1: Sending env LC_TELEPHONE = en_US.UTF-8
debug1: Sending env LC_CTYPE = en_US.UTF-8
Last login: Tue Feb 14 15:36:45 2017 from 192.168.23.208

Try `df --help‘ for more information.
[root@newhis1 ~]# 

 当更改LANGUAGE等变量时,系统自带的命令的输出也会变:

[root@newhis1 ~]# LANGUAGE=zh_TW
[root@newhis1 ~]# df -abcdefg
df: 不適用的選項 -- b
請嘗試執行‘df --help’來獲取更多資訊。
[root@newhis1 ~]# LANGUAGE=zh_CN
[root@newhis1 ~]# df -abcdefg
df:无效选项 -- b
请尝试执行“df --help”来获取更多信息。
[root@newhis1 ~]# LANGUAGE=en_US
[root@newhis1 ~]# df -abcdefg
df: invalid option -- b
Try `df --help‘ for more information.
[root@newhis1 ~]# 

 当Terminal的字符集包含以上输出所含的字符时,就可以显示正常。无法显示时,就会出现乱码。

 比如Terminal选的是gb2312,而LANGUAGE选择的是zh_TW,程序输出为:

技术分享

其中打?号的部分就是乱码,之所以还可以看到部分汉字,是因为gb2312的字符集中还包含部分繁体字(繁体中没有简化的字)。

同理,如果terminal选的是UTF-8,这个字符集包含了全球可见字符,所以中文、德文都能显示。

比如:

[root@newhis1 ~]# cat a.sh 
LANG=ru_UA.utf8
LANGUAGE=ru_UA
LC_CTYPE=ru_UA.utf8
LC_NUMERIC=ru_UA.utf8
LC_TIME=ru_UA.utf8
LC_COLLATE="ru_UA.utf8"
LC_MONETARY=ru_UA.utf8
LC_MESSAGES="ru_UA.utf8"
LC_PAPER=ru_UA.utf8
LC_NAME=ru_UA.utf8
LC_ADDRESS=ru_UA.utf8
LC_TELEPHONE=ru_UA.utf8
LC_MEASUREMENT=ru_UA.utf8
LC_IDENTIFICATION=ru_UA.utf8

[root@newhis1 ~]# source a.sh

 

技术分享

 

Windows Git-shell

解决Linux终端乱码的两则例子