您的位置:首页 > 运维架构

运维日记003-那些曾经令人头痛的乱码

2016-07-18 17:55 381 查看

运维日记003-那些曾经令人头痛的乱码

“回车”与”换行“

在Linux上用vi编辑好的文件,传给别人的Windows电脑上后,打开发现所有的文字都变成了一行;反之,Windows系统上编辑好的文本文件,拿到Linux上也会出现问题。什么原因呢?

在计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33)的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这个字符将丢失。

于是,研制人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。一个叫做“回车”(,在C语言中用“\r”表示,对应的ASCII码为0x0D),告诉打字机把打印头定位在左边界;另一个叫做“换行”(,在C语言中用”\n”表示,对应的ASCII码为0x0A),告诉打字机把纸向下移一行。这就是“换行”和“回车”的来历,从它们的英语名字上也可以看出一二。

后来,计算机发明了,这两个概念也就被般到了计算机上。那时,存储器很贵,一些科学家认为在每行结尾加两个字符太浪费了,加一个就可以。于是,就出现了分歧。UNIX一族决定只用进纸一个字符来表示行尾。来自苹果阵营的人则把回车作为换行的标准。MS-DOS(和微软的Windows)仍然决定沿用古老的回车换行传统。所以,Unix/Linux下的一些传统软件以0x0A作为换行标志,而Windows下的软件以0x0A0D作为换行标志。这样造成的直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。

举个例子,写一小段c++代码:

$ vim a.cpp


代码内容:

#include <iostream>

using namespace std;

int main()
{
cout << "hahaha" << "\r\n" << "xixi" << "\r\n";
}


运行该程序,用vi打开输出的内容:

$ g++ a.cpp
$ ./a.out > a.txt
$ vi a.txt


可以看到两个”^M“的符号。而在Windows系统下用记事本打开则完全正常。用hexdump查看a.txt文件,可以看到两处0D0A标志。

$ hexdump a.txt


0000000 6168 6168 6168 0a0d 6978 6978 0a0d
000000e


处理这类乱码和串行问题可以使用unix2dos和dos2unix两个小工具。在RHEL6/CentOS6下我们用如下命令安装:

# yum install unix2dos
# yum install dos2unix


这两个工具使用的方法也很简单:

$ dos2unix -n a.txt b.txt


上面这条命令把Dos/Windows下的文本文件a.txt转换为Unix/Linux下的文本文件b.txt。可以用hexdump打开b.txt观察一下,可以发现0A0D的标志都换成了0A标志。unix2dos的用法与之类似。

字符编码与iconv编码转换

字符编码的基本概念

ASCII编码,就是英文显示文字所需要的256个字符(比如,英文字母、数字、标点符号等等)

ANSI编码,像中文,肯定不能只用256个字符就代表所有汉字。因此对ASCII码表进行了扩展,使用两个(或多个)字节,代表一个汉字。类似的,不同的国家和地区制定了不同的标准,这些使用 2 个字节来代表一个字符的各种延伸编码方式,称为 ANSI 编码。也就是说,ANSI是一种对ASCII码表进行扩展的泛称,不同语言操作系统,其代表的编码方式不一样。比如中文操作系统,ANSI编码就代指GB2312;日文操作系统ANSI编码就代指JIS。

Unicode编码,Unicode是一个超大的集合,也是一个统一的标准,可以容纳世界上的所有语言符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,“汉”这个字的Unicode编码是U+6C49。

代码页(codepage),Unicode是一个世界统一的标准,也就是说,如果一个文本是用Unicode方式编码的,那么它可以同时显示中文、日文、阿拉伯文等等,并且是在任何系统上都可以正常显示的。但是由于ANSI编码之间互不兼容,因此就需要有一个标识来表明不同的ANSI编码到Unicode之间的映射关系(也就是不同编码之间的映射关系),这个就是代码页。比如简体中文的代码页是CP_936(中文系统默认的代码页),这个也就是windows API中MultiByteToWideChar第一个参数所代表的含义。如果不标明代码页,系统是不知道如何进行编码转换的。

SBCS(单字节字符集),MBCS(多字节字符集),DBCS(宽字节字符集),分别对应上面提到的ASCII编码、ANSI编码、Unicode编码。

中文常见编码:GB2312(CP_20936)->GBK(CP_936)->GB18030(CP_54936),三种编码方式向下兼容,也就是说GB18030包含GB2312的所有字符。GB18030在2000年取代GBK成为正式国家标准。

UCS(Unicode Character Set):UCS-2规定了2个字节代表一个文字,还有UCS-4规定了4个字节代表一个文字。我们工作中几乎总是在和UCS-2打交道。

UTF(UCS Transformation Format):UCS只是规定的如何编码,但是没有规定如何传输、保存这个编码。UTF则规定了由几个字节保存这个编码。UTF-7,UTF-8,UTF-16都是比较常见的编码方式。UTF-8编码与Unicode编码并不相同,但是它们之间可以通过计算进行转换,而不像ANSI和Unicode之间必须通过一个映射表来人为规定其对应关系。UTF-16完全对应于UCS-2,并可通过计算代表一部分UCS-4文字。还有UTF-32则是完全对应于UCS-4,不过很不常见就是了。

UTF-8是与ASCII码兼容的,英文字母1个字节,汉字通常是3个字节

;UTF-16的所有字符都是用2个字节进行保存,其编码与Unicode是等价的。UTF-16又分为UTF-16LE(little endian)和UTF-16BE(big endian),比如一个字母’a’,如果按utf-8来存,就是0x61;如果按utf-16le来存就是0x61 0x00(低有效位在前);如果按utf-16be来存就是0x00 0x61(高有效位在前)。这个也就是我们用记事本另存文件的时候可以选择的几个编码方式的含义。

BOM(byte order mark),上面提到的utf-8 utf-16le utf16-be都是unicode编码,但是系统依然无法正确解析一个文本文件,即便已经知道它是unicode编码。所以就有了这样的规定:在文本文件的最开头插入几个字节的标识,来说明编码方式。utf-8的BOM是0xef 0xbb 0xbf,utf-16le的BOM是0xff 0xfe,utf16-be的BOM是0xfe 0xff。事实上BOM并不是必须的,它仅仅是帮助程序自动判断编码方式使用的,如果我们手动选择编码方式(像ANSI一样),即便没有BOM,也是可以正常显示的。反过来说,程序读文本文件的时候要先读文本开始的三个字节判断下编码方式。

iconv文件编码转换

iconv工具可以方便的在不同的字符编码之间进行转换。其命令用法为:

iconv [选项…] [文件…]

有如下选项可用:

输入/输出格式规范:

-f, –from-code=名称 原始文本编码

-t, –to-code=名称 输出编码

信息:

-l, –list 列举所有已知的字符集

输出控制:

-c 从输出中忽略无效的字符

-o, –output=FILE 输出文件

例如,要将文件readme.txt由GB2312编码转换为UTF-8编码,使用如下命令:

$ iconv -f gb2312 -t utf8 ./readme.txt -o ./readme_utf8.txt


另外,系统还提供了一个iconv函数可供编程时调用,用于解决编码转换问题。具体使用方法可查看手册:

$ man 3 iconv


注:以上内容有较多篇幅摘抄自互联网,在此向作者表示感谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  运维