Linux 下的编码问题

Linux 下最容易出现的就是乱码问题,因为我们的母语不是英语,从而导致此类问题的出现。而这其中最主要就是字符集及其对应的编码的问题。

计算机只认识 0/1,而人类认识的却是各种字符,这中间的转换就是通过字符编码来实现的。

字符编码将字符集在人和计算机之间做转换,大家常见的字符集包括 GB2312,BIG5,Unicode 等。

ASCII(American Standard Code for Information Interchange)是最原始的字符集,对于英语母系的人来说足够用了;而 GB(Guóbiāo,GB)开头的是为了满足国人要求而制定的。他们之间的区别在于 GB2312 的字符集不够大,不能满足计算机处理汉字的需要,所以出现了 GBK 以及 GB18030 等字符集,而后者是目前国内最新的的编码集合;Big5 的出现是为了满足繁体中文需求而制定的一套字符集。

每个国家都搞一套自己的编码肯定不利于标准的发展,出现乱码很大一部分是这个原因,使用 Big 5 的网页用西欧的某个字符集去解码当然是乱码,反之亦然。于是出现了 Unicode(UniversalMultiple-Octet Coded Character Set)统一编码集,而他的字符编码却有 UTF-8/16/32 等几种方式。

目前用的最多的字符编码就是上面提到的 UTF-8(8-bit Unicode Transformation Format),他的多种优点使得它逐渐成为电子邮件,网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联 网协议都必须支持 UTF-8 编码。互联网邮件联盟(IMC)建议所有电子邮件软件都支持 UTF-8 编码。

在 Linux 下,字符集默认是放在 /usr/share/i18n/charmaps 下的,将 .gz 解压缩就可以看到一个一个的字符集合了。

关于编码更多的介绍可以参看 wiki 上的词条

可以通过 locale 查看当前本地的语言环境:

# 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=

可以看到一共有 14 个变量,他们都是按照本地的文化等因素来定义的。比如 LC_TIME 就定义了本地的时间表示方式。具体的是定义在 /usr/share/i18n/locales/ 目录下。而这个变量表示的意思就是身处 US 使用 en 也就是使用英语的,通过 UTF-8 编码的方式来表示时间。再比如 zh_TW.GIG5 的意思就是在台湾的说中文的同学使用 BIG5 编码方式来解释字符。

在这 14 个变量中,根据优先级大致分为 2 类,优先级最高的 LC_ALL,次高的 LC_*,最后的 LANG 有点类似 iptables 的默认值,如果 LC_* 没有设置,则使用 LANG 这个变量。而如果 LC_ALL 设置了的话,会覆盖 LC_* 的值,但是不影响 LANG。

假使 LC_ALL=zh_CN.UTF-8,那么其余的即使了设置了也没效果,因为 LC_ALL 优先级最高。

而有的时候,经常在脚本中看到:

LC_ALL=C

这个其实是为了去除本地化的设置,统一使用 POSIX 标准,它是 C 的别名。C 是 Linux 设置的默认 locale。如果是最小化安装 Linux 的话,LC_ALL 应该就是此值。

另 外我们还经常能看到 LANGUAGE 这个变量,与 LANG 不同的是,LANGUAGE 主要针对的大多数的应用程序的本地化,而 LANG 则是系统上 LC_* 或是 LC_ALL 未设置时的默认值。这两个区别不是很大,很多的发行版已经现在已经将此合并了。

我们下面来看看这张图

出现乱码的原因很简单,我在 firefox 的 View -> Character Encoding 中选择了用 GB2312 来解析我使用 UTF-8 来编的码,当然是乱码了

再来看看下面这两张图

 

 

上图 rhel 5.1 的 gnome 界面,可以发现虽然也是乱码,但是好像比上面的乱的稍微有规律一些,都是一些框框而不像上面的毫无规律。下图是 firefox 的界面,不管将 Character Encoding 设置成什么都是乱码,这其实已经不是字符集的问题,字符集已经能支持了(Linux 默认使用 UTF-8),出现的问题的是字体的问题,rhel 上没有一个中文字体,当然显示不出来。

我们安装一个简单的 yum 包,注销重新登陆就可以了:

# yum install fonts-chinese

现在再看看,可以发现这个 CAS 使用的是 GB2312 编码

rhel 的出现乱码的情况是我们在 /etc/sysconfig/i18n 中将 LANG 设置成了 zh_CN.UTF-8 了,没有相应的字体导致的情况,如果设置成 en_US.UTF-8 就不会出现这种情况了。

除了在 i18n 这个文件下修改之外,也可以在 profile 或者 bashrc 类似的文件中直接写入:

export LANG="en_US.UTf-8"

那么上面提到 /usr/share/i18n/locales 和 /usr/share/i18n/charmaps 又有什么关系了?可以通过这两个目录下的文件以及 localdef 来生成我们需要的 locales:

#localedef -i zh_CN -f GB18030 zh_CN.GB18030
在 locales/ 下找到 zh_CN,在 charmaps/ 下找到 GB18030,组合成 zh_CN.GB18030 这个 locale,将其追加到 /use/lib/locale/locale-archive 文件中。

上面那条 yum 命令的要做的事情就是直接帮我们在 /usr/share/fonts/ 下新建了几个字体目录,我们也可以自己着到合适的字体,不一定要依赖官方提供的这个 fonts-chinese:

# mkdir /usr/share/fonts/new
# cp ~/your-fonts /usr/share/fonts/new
# cd /usr/share/fonts/new

下面这两条命令都是为了建立字体的索引,首先生成 fonts.scale 文件:

# mkfontscale

生成 fonts.dir 文件:

# mkfontdir

更行缓存:

# fc-cache -fv

或者使用:

# server xfs restart

其他的比如 gedit 乱码,putty 乱码等问题都跟上面说的有关,举一反三。

总结一下,Linux 下的乱码问题,不但跟字符编码有关系,也跟字体的支持有关,需要二者的配合才能显示出满意的画面。

下面简单的说说转换编码格式的方法,主要由 convmv 或者 glibc 中提供的 iconv 负责转换:

# convmv -f UTF-8 -t GBK –notest file
# iconv -f utf-8 -t gbk file > file.GBK

这里是一枚转换脚本

以上只是针对于个人的桌面系统,服务器相比之下问题就少多了,一般默认设置 LC_ALL=en_US.UTF-8 就可以了。