乱码!乱码!在linux终端上不小心或不在意cat了一个elf文件,或者仅仅是cat了一个word文档,整个shell就成了乱码,连提示符都不例外,很多人包括我之前都是索性关掉shell,然后重新打开一个shell,这linux的终端未免也太脆弱了吧,一向宣称进程间隔离的linux系统,怎能对待如此重要终端如此如此,盈天地之唯美,只在一草一木之间,然linux却如何做到。
事实上,在彻底解决问题之前,我不得不先为linux平反,首先造成终端乱码的不是linux设计的漏洞,而是用户的有意为之,另外即使用户有意为之,如果你换一下终端类型,cat同样的二进制文件就可能不再乱码,最后,要记住,不是linux本身出了问题,问题在于字符集。
用户的有意为之怎讲?用户并没有对终端进行设置啊!是的,用户没有显式的设置,可是却隐式地进行了设置,此所谓隐式是cat或者vi等“文本”查看/编辑器对终端进行了设置,想理解这个就必须对终端有一个深入的理解,这里不再介绍,需要者自然google之。每个终端为了回显字符必有一个字符集配置,每个终端为了实现功能,必有一个命令解释规范,对于最古老的vt100终端类型来说也是这样,如果很多终端对vt100兼容,vt100有两种字符集设置,称为G0和G1,注意,这G0和G1只是一个中间层,并没有指向最后的字符映射表,用户可以为每一个字符集配置其指向的字符映射表,这字符映射表才最终地进行了字符显示时的解释。我们在解决问题之前先来手动制造一次乱码,解决错误之前首先明白为什么错。要手动制造乱码,由刚刚介绍的理论得知,只要将G0或者G1指向一个错误的字符映射表就可以了,但是怎么做到呢?于是我们就不得不熟悉一下相关的vt100的控制命令了,vt100的控制命令均是以ESC键开头,后面跟一个键序列,比如ESC+[+2+J的意思就是清屏,那么我们测试一下这个清屏命令,用echo -e来测试,esc键是0x1b,于是echo -e '/x1b[2J'就实现了清屏,既然知道了vt100的控制命令写法,我们接下来来一次手工乱码,还是先来一段理论,很多系统用四张字符映射表,分别设为a,b,c,d,一般a为我们使用的映射表,具体这四张映射表分别有何属性,本文不再介绍,请google之,关键词为vt100,那么只要我们将字符映射表设置为b,c,d就可能造成乱码了,我们不妨试一下,配置字符映射表的命令是ESC+(B/0/U/K(G0字符集)和ESC+)B/0/U/K(G1字符集),于是我们来一个echo -e '/x1b(0',看看是不是乱码,如果不是就说明当前的字符集可能是G1,那么就试试echo -e '/x1b)0',很多人看到的结果已经是乱码了,这就说明我们手工成功制造了乱码事件,怎么改回来呢?十有八九是echo -e '/x1b(B'或者echo -e '/x1b)B',注意以上命令可以用重定向的方式进行,比如echo -e '/x1b(0'>/dev/ttyX等等,否则一旦终端乱码了,你也就只能盲打了。
于是,很多问题不用再解释了,cat或者vi本来就不是让你用来处理二进制文件的,以cat为例,它基本就是下面的处理过程:
while(...) {
read(...);
write(..)
}
在write的时候,一旦二进制序列中用上面的echo -e '/x1b(0'(或者U,K)那就麻烦了,cat在你不知道的情况下将'/x1b(0/u/k'写入了终端,而所有人都不能保证你的二进制文件中没有那样的序列,但是如果那是文本文件,除了你指明的转义,一个"/x"一般就被解释成了一个字符串而不是控制字符,也就是说,文本文件的任何处理都是用户指定的,用cat/vi处理二进制文件本身就是误用。在你使用vt100终端的情况下,cat /usr/bin/test试一下吧,然后再echo -e '/x1b(B'恢复之,另外在终端上,我们也可以使用快捷键,比如ctrl+v+n可以将字符集从G0切换到G1,如果G0和G1的字符映射表设置得不同或者不兼容,那么就会出现乱码,用ctrl+v+o恢复之,其实这里的ctrl+v+n/o和/xe//xf是一回事,具体可以查看一下键码的知识。用vi我们也可以测试一下,vi一个文件,然后按ctrl+v,表明接下下插入控制字符,之后按ctrl+n,然后正常输入字符,保存,退出,然后cat该文件,乱之!
事实上,在彻底解决问题之前,我不得不先为linux平反,首先造成终端乱码的不是linux设计的漏洞,而是用户的有意为之,另外即使用户有意为之,如果你换一下终端类型,cat同样的二进制文件就可能不再乱码,最后,要记住,不是linux本身出了问题,问题在于字符集。
用户的有意为之怎讲?用户并没有对终端进行设置啊!是的,用户没有显式的设置,可是却隐式地进行了设置,此所谓隐式是cat或者vi等“文本”查看/编辑器对终端进行了设置,想理解这个就必须对终端有一个深入的理解,这里不再介绍,需要者自然google之。每个终端为了回显字符必有一个字符集配置,每个终端为了实现功能,必有一个命令解释规范,对于最古老的vt100终端类型来说也是这样,如果很多终端对vt100兼容,vt100有两种字符集设置,称为G0和G1,注意,这G0和G1只是一个中间层,并没有指向最后的字符映射表,用户可以为每一个字符集配置其指向的字符映射表,这字符映射表才最终地进行了字符显示时的解释。我们在解决问题之前先来手动制造一次乱码,解决错误之前首先明白为什么错。要手动制造乱码,由刚刚介绍的理论得知,只要将G0或者G1指向一个错误的字符映射表就可以了,但是怎么做到呢?于是我们就不得不熟悉一下相关的vt100的控制命令了,vt100的控制命令均是以ESC键开头,后面跟一个键序列,比如ESC+[+2+J的意思就是清屏,那么我们测试一下这个清屏命令,用echo -e来测试,esc键是0x1b,于是echo -e '/x1b[2J'就实现了清屏,既然知道了vt100的控制命令写法,我们接下来来一次手工乱码,还是先来一段理论,很多系统用四张字符映射表,分别设为a,b,c,d,一般a为我们使用的映射表,具体这四张映射表分别有何属性,本文不再介绍,请google之,关键词为vt100,那么只要我们将字符映射表设置为b,c,d就可能造成乱码了,我们不妨试一下,配置字符映射表的命令是ESC+(B/0/U/K(G0字符集)和ESC+)B/0/U/K(G1字符集),于是我们来一个echo -e '/x1b(0',看看是不是乱码,如果不是就说明当前的字符集可能是G1,那么就试试echo -e '/x1b)0',很多人看到的结果已经是乱码了,这就说明我们手工成功制造了乱码事件,怎么改回来呢?十有八九是echo -e '/x1b(B'或者echo -e '/x1b)B',注意以上命令可以用重定向的方式进行,比如echo -e '/x1b(0'>/dev/ttyX等等,否则一旦终端乱码了,你也就只能盲打了。
于是,很多问题不用再解释了,cat或者vi本来就不是让你用来处理二进制文件的,以cat为例,它基本就是下面的处理过程:
while(...) {
read(...);
write(..)
}
在write的时候,一旦二进制序列中用上面的echo -e '/x1b(0'(或者U,K)那就麻烦了,cat在你不知道的情况下将'/x1b(0/u/k'写入了终端,而所有人都不能保证你的二进制文件中没有那样的序列,但是如果那是文本文件,除了你指明的转义,一个"/x"一般就被解释成了一个字符串而不是控制字符,也就是说,文本文件的任何处理都是用户指定的,用cat/vi处理二进制文件本身就是误用。在你使用vt100终端的情况下,cat /usr/bin/test试一下吧,然后再echo -e '/x1b(B'恢复之,另外在终端上,我们也可以使用快捷键,比如ctrl+v+n可以将字符集从G0切换到G1,如果G0和G1的字符映射表设置得不同或者不兼容,那么就会出现乱码,用ctrl+v+o恢复之,其实这里的ctrl+v+n/o和/xe//xf是一回事,具体可以查看一下键码的知识。用vi我们也可以测试一下,vi一个文件,然后按ctrl+v,表明接下下插入控制字符,之后按ctrl+n,然后正常输入字符,保存,退出,然后cat该文件,乱之!
好了,这下问题解决了,那么能否彻底解决这个问题呢?事实上可以的,为了防止二进制文件中的转义序列的write写入将G0切换到G1,那么就将G0和G1的字符映射表设置成同一个,为了防止二进制转义序列改变G0或者G1的字符映射表,那么就在每次处理完的时候重新恢复字符映射表的原始设置,不过,最好额办法不是为系统增加系统的鲁棒性,而是让各个程序各司其职,不要误用。还有一种方式就是使用健壮性比较好的终端,比如ansi以及linux终端等等,而放弃使用vt100兼容终端,使用rset程序修改之。
本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1271868