之前通过反复设置LANG变量为"en_US.UTF-8"和"zh_CN.UTF-8"来切换中英文man手册时手误将"zh_CN.UTF-8"写成了"zh-CN.UTF-8",系统应该是解析该字符编码名失败后静默采用了"en_US.UTF-8"导致bash显示中文字符为数字编码:
设置回正确的LANG变量后会正常显示中文字符:
百度之后才发现这是UTF-8的八进制编码,索性就搞清楚点编码格式。
以下转自汉字编码及LINUX中文处理--转(括号里是本人拙见)
英文字符使用256个ASCII字符(32个不可打印与96个不可打印字符+128个扩展的符号)即一个byte表示即可,前128个符号只占用后面7位,最前面的1位为0;后128个符号最前面的1位为1,;
汉字用一个byte表示明显不够(汉字字符数太庞大了),因此最早的汉字编码是采用两个byte来表示。同时为了与原来ASCII英文字符不混淆(2字节字符肯定会"包含到"1字节字符),特别规定:
两个大于127的字节(第1位为1)连在一起时,就表示一个汉字,前面的一个字节(称为高字节)从0xA1(十进制161)到0xF7(十进制247),后面一个字节(低字节)从0xA1到0xFE(十进制254),这样就能组合出(247-161+1)×(254-161+1)=8178个组合,大约7000多个简体汉字+(数学符号+罗马字母+希腊字母+日文假名等),连带ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码(这些字符自然比ASCII字符编码多占一个字节,好与简体汉字对齐统一被编码),这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。这个方案称为GB2312编码,一般称为国标码。
在计算机于中国开始普及年代,如DOS/WIN3.1/Win95等阶段。GB2312立下了汗马功劳。在同一时期,以繁体字为主的台湾也搞了一套类似的编码,称为BIG5编码(繁体中文大五码),原理是一样的,但是与GB2312码不相互兼容(需转换)。
BIG5编码参考繁体中文大五码(Big5)编码详解
BIG5编码每个字也占据2bytes,第一个字节在0xA4-0xC6之间的基本都是常用字区,繁体中文最常用的5401个字的编码值域在此范围内。第一位在0xC9-0xF9之间的区域为次常用字区,繁体中文不常用的7652个字存储在此范围内。此外还有一些造字区、保留区,造字区的本意是官方并不规定此值域内的具体文字,留给后续应用程序或操作系统自己定义。保留区的本意是此区域保留,不做编码使用。然而后来随着BIG5编码的不断完善,BIG5-2003版本发布后,正式将很多定义在保留区和造字区的常用字列入了官方标准当中。例如0xA3E1在上图中显然是属于保留区,但在后来该值在BIG5-2003中被定义为欧元货币符号"€"。
比如第一个字节如果是0x00-0x7F,则表示的是BIG5与ASCII重合部分(此时第二个字节忽略,这与GB2312两个字节都大于127{第1位为1}不同)。第一个字节在0xA1-0xA3的区域为BIG5编码的标点符号和特殊符号区域。比如句号是'0xA143",冒号是"0xA147",问号是'0xA148",希腊字母θ是'0xA363",它们的第一个字节都位于0xA1-0xA3。
GB2312也有很多缺点,一是字符集太小,只能表示几千个常用汉字。大量汉字没法表示,而是与其他汉字表示集不兼容,经常出现乱码的情况。(不兼容或者不冲突的意思是存在相同的两字节的值分别在GB2312与BIG5中对应着不同的内容,所以除非出现一种编码能同时将GB2312与BIG5一一映射到其自己的编码中并保证不冲突,这样这个编码就能同时存储这两种编码的内容而不引起歧义了,对其他语言的文字亦是如此。乱码就是A码将B码的内容按照A码的规则去翻译,自然就乱来了)
UNICODE表示
为了解决全世界文字冲突问题,国际标准组织ISO把所有语言的字符,全部统一编码(就是所有字符的值都不同,自然不会冲突),称为"Universal Multiple-Octel Coded Character Set",简称UCS,或简称Unicode方案。
Unicode规定所有字符包括英文字符由两个字节表示,但为了兼容,原来的ASCII码采用高位字节全部取0低位字节保持为原来ASCII值的方式。
需要注意的是,Unicode只是一个一个符号集,它只规定了符号的二进制表示,却没有规定这个二进制代码应该如何存储(大小端序)。我们一般称为Unicode表示(而不是Unicode编码),是指用两个byte,即一个short来表示的方法,2个字节在32位CPU上正好是一个short型,这样在不同字节序的CPU下,同一个编码有两种排序,有小端序(低位字节在前)和大端序(高位字节在前),两种方案分别称为Unicode、Unicode-Big。
显然Unicode表示不与GB2312或Big5兼容,仍有冲突。
Unicode在制定时没有考虑与任何一种现有的编码方案保持兼容,这使得GBK与Unicode在汉字的内码编排上完全是不一样的,没有一种简单的算术方法可以把文本内容从Unicode表示和另一种编码进行转换,这种转换必须通过查表来进行。
UTF-8
随着互联网兴起,如何在网络上传输unicode字符也是一个问题,于是面向传输的众多UTF(UCS Transfer Format)标准出现了,顾名思义,UTF8就是每次8个位传输数据,而 UTF16就是每次16个位。
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母(包含在内码32-126的可打印字符中,第一位同样为0),UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
下表总结了编码规则,字母x表示可用编码的位。
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
----------------------------|---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
结合:你真的懂 Unicode 和 UTF-8 是什么关系吗?来看看这个就彻底懂了!
上表可以这样看:由于UTF-8是变字节的编码,一、二、三、四个字节在固定了规则限定的若干位后,按从右到左的顺序存储Unicode字符的二进制位;
UTF-8的有效位按其能表示的对象个数(二进制位的排列组合数),分配一定数目(小于等于对象个数)的Unicode字符,例如二字节的UTF-8编码"110xxxxx 10xxxxxx"能有2^11中组合方式,也就能最多标定2^11=2048个Unicode字符,对应Unicode表示的字符为0x0080-0x07FF,但Unicode表示不是连续的,中间有空当,所以该区间的字符数肯定不等于3777-128+1个,一定是小等于2048个。
现在可以手撕一下最开始遇到的中文字符"年"被UTF-8书写成'$'\345\271\264的出处了:
首先百度中文字符"年"的Unicode表示为'\u5e74',这是一个16进制表示,对应的二进制表示是"0101 1110 0111 0100",将该二进制按从右到左顺序填充到UTF-8规划好的三字节的有效位中,把所有16位二进制位一一填充到三字节空出来的4+6+6共16个二进制位:
11100101 10111001 10110100
再把这三个字节分别用八进制表示成345、271、264,这就是linux表示中文"年"的规则。
GBK
GBK编码(Chinese Internal Code Specification)是中国大陆制订的、等同于UCS的新的中文编码扩展国家标准。GBK编码能够用来同时表示繁体字和简体字,而gb2312只能表示简体字,GBK是兼容gb2312编码的。GBK工作小组于1995年10月,同年12月完成GBK规范。该编码标准兼容GB2312,共收录汉字21003个、符号883个,并提供1894个造字码位,简、繁体字融于一库。Windows95/98简体中文版的字库表层编码就采用的是GBK,通过GBK与UCS之间一一对应的码表与底层字库联系。
(中国大陆)一般有时把ASCII与GB2312混排称为ANSI,它与UTF-8,UNICODE,UNICODE BIG称为四种编码。
ANSI
使用0x00-0x7F(0-127)范围的1个字节表示1个英文字符,超出此范围的使用0x80-0xFFFF来编码,比如简体汉字"中"在简体中文操作系统中(如上VS2010项目属性选择"多字节字符集")使用[0xD6,0xD0]这两个字节存储。
20200526,当项目属性分别使用"多字节字符集"、"Unicode字符集"时,_T()宏将字符串字面值按"ANSI(GB2312)码"、"Unicode码"进行编码,L宏始终将字符串字面值按Unicode码进行编码,不加任何宏的字符串"xxxxx"始终按ANSI(GB2312)码对单字节字符或宽字符进行编码;
在调试监视变量时VS2010将对char值以"ASCii码"解释(就是光标移动到变量时的解释值),对wchar_t值以"Unicode码"解释。此时,当宽字符被转成char后(比如ATL/MFC的CStringW转char[])进行解释会因匹配不到ASCii码而在编译器中显示'?'。赋给char[]或TCHAR[](项目使用多字节字符集时)的宽字符的字面值将按ANSI(GB2312)码存储,即将宽字符的两个字节当做两个"单字节ASCii字符"存储,但翻译时尝试按ASCii码进行解释,找不到时编译器会显示'?',ASCii字符仍按ASCii码存储;项目使用Unicode字符集时,赋给wchar_t[]或TCHAR[](项目使用Unicode字符集时)的宽字符的字面值将按Unicode码存储,翻译时自然按Unicode码进行翻译,编译器自然能正确显示;
不同的国家和地区制定了不同的标准,由此产生了GB2312、GBK、GB18030、Big5、Shift_JIS等各自的编码标准。这些使用多个字节来表示一个汉字字符的各种汉字延伸编码方式,称为ANSI编码。在简体中文Windows操作系统中,ANSI编码代表GBK编码;在繁体中文Windows操作系统中,ANSI编码代表Big5;在日文Windows操作系统中,ANSI编码代表Shift_JIS编码(日文保留有不少汉字)。
简单的说,在简体中文系统下,ANSI编码代表GB2312编码(ASCII+GBK的简体中文部分也即GB2312);在日文操作系统下,ANSI编码代表JS编码。
不同ANSI编码之间互不兼容,ANSI编码表示英文字符时用一个字节,表示中文用两个或四个字节。
ANSI编码作为中国以及部分亚太地区的多字符编码格式,Windows 系统和OS X都是提供原声支持的。但即便如此,许多国外开发者仍然在开发笔记或文字录入类应用时将ANSI编码完全忽略,只加入全球通用的UTF-8编码。
文本文件类型
因为并存好几种编码,软件处理文本文件是如何判断类型的?实际如果不加一些附加信息,软件也难判断文本是哪一种格式.一般是文件前面加入字节序标记BOM(Byte Order Mark)码。其中有下表:
字节序 | 编码格式 |
00 00 FE FF | UTF-32,big-endian |
FF FE 00 00 | UTF-32,little-endian |
FE FF | UTF-16,big-endian/即一般的UNICODE-BIG |
FF FE | UTF-16,little-endian/即UNICOdE |
EF BB BF | UTF-8 |
这样只要打开文本文件内容,查看前四个字节表示的字节序,就能判断出是哪一种格式。如果全都不是,意味着是ANSI编码(单字节ASCII码+双字节GB2312码,前者应该是0-127的ASCII字节码,首位为0。后者是两个大于127的GB2312字节码,首位均为1。)
一些编码格式一般是约定成俗的,如*.lrc,往往使用ASCII与GB码混排,即ANIS格式。C源程序采用ASCII。但中间的汉字在不同系统下有不同规定LINUX采用UTF-8,WINDOWS采用GB码。
Windows/Unix 文本回车符.
处理文本文件还有一个细节,是WINDOWS下敲入回车会输入回车符\r+换行符\n两个字符即"\r\n",而UNIX敲回车只输入回车符"\n",而它还有一个特殊规定,最后一行是空行,即文件结尾以两个换行符"\n\n"结尾。大部分软件能识别两种格式,但是WINDOWS记事本打开LINUX文本文件时,并不换行而是显示黑框。
但fgets之类实现是最早在UNIX实现,所有读出来最后的字符都是\n,WINDOWS上也是如此,必须用fopen(name,"rb")然后用fread才能读出\r\n来。
标准C宽字节类型
strlen(),计算结果只针对ASCII单字节字符的串有效,对于2个byte字节的符号无法处理。因此标准C中,制定了wchar_t,它一般被定义成unsigned short 型。对宽字节的函数要采用新的w打头的接口:
如求宽字符字符串的长度的函数是
#include <wchar.h> size_t wcslen(const wchar_t *s); |
Linux 下汉字处理
Linux 下的汉字编码是以UTF-8,这从RHEL的中文版的语言就可以知道,中文环境下,环境变量LANG的值为 zh_CN.UTF8,而WINDOWS自WINDOWS NT开始,内部都采用UNICODE表示。因此两者在编程有着细微差别。
如果是带汉字的文本可参考相应的BOM来处理。但是在显示、打印时要因应不同的系统采用不同格式。
如LINUX下控制台下,需要UTF-8格式才能正确处理。象SDL的图形界面下,汉字要转成UNICODE或UTF-8才能正常显示。文本文件还好处理,但如果在C代码里出现汉字常量,如printf("黄新宇\n");这个有不同的情况。如果这个C代码是在LINUX编辑,则C源程序中是UTF-8直接可以显示,如果这个C代码是在WINDOWS编辑拷贝过来。则很有可能是GB码,这样程序编译后在LINUX控制台上乱码。
另外LINUX中文文件名也是UTF-8编码的,可以在fopen中直接采用中文编码。如果WINDWOS的中文则有可能是UNICODE编码,但在samba拷贝后会自动转换。
原文地址 http://blog.chinaunix.net/u3/105675/showart_2121442.html