初学Linux使用的人们,坐在Linux老手旁边,看他们帮自己解决一些工作上的问题时,往往会惊叹于他们似乎不怎么使用GUI。而更让你惊讶的是他们频繁使用“管道”和“I/O重定向”的频率。这让你总是有一种莫名的敬佩感不知觉地从心中幽幽然升起。 其实,Linux老手们并不是刻意要在你们面前显摆他们有多么的牛x而去使用让你眼花的文本界面;更不是为了在你们面前装13而大量使用“管道”和“I/O重定向”。只因这样更简单、更灵活、更强大、更具效率,更符合Linux的使用方式和设计特点。 也许你还没有察觉,或是并没有太在意。不仅仅老手们喜欢使用文本界面,甚至在Linux中除了那些能够执行的程序文件之外,其他的一切几乎都是文本的。如存储用户名和密码的passwd文件,存储图片的xmp 文件等,甚至一些程序也是文本的。Linux中提供了好多与文本文件相关的命令,如cat、grep、more、diff、head、tail等,举不胜举。而且它们从来不生成什么文件,只将处理结果输出到屏幕上。即便要生成文件,也只能利用“IO重定向”来达成目的。 Linux给人的感觉是它非常讨厌即轻巧又快捷的二进制,偏好即笨重又低效的文本,是不是很傻啊?可是这些并不是Linux的专利,Unix也是这样! 4.2.1 二进制的烦恼 对于计算机程序而言,二进制数据是最容易处理的数据,因为计算机就是二进制的。但是二进制数据并不适合于人类的阅读和传播。 首先,由于人的祖先给人类遗传下来了十根手指,导致人能非条件反射地搞清楚10进制而搞不清楚二进制;其次,即便都是认识二进制的计算机,由于内部数据格式约定的不同(如高字节优先对低字节优先,或32位对64位)还会导致数据无法互用的问题。 为了解决人看不懂的问题,人们发明了大量二进制文件查看器。但似乎总是跟不上历史的脚步,过不了多久就会有那么几种被淘汰。少数能够保留下来的,也无法让人一目了然。人们只能继续去做大量的重复而又没有创造性的劳动。 为了解决二进制数据的互用问题,人们发明了好多种序列化与反序列化的方法。但经常遇到的麻烦就是,即使是一个很小的程序,序列化与反序列化的代码也会导致程序变得臃肿肥大。虽然Python和Java这样的现代语言内置了这种机制从而大大减少了工作量,但也因为种种原因,很多时候不能让人如意。最常见的情况就是数据传输的两端必须保持严格的一致,否则就会产生各种潜在的问题,而且这些问题很难被发现。尤其是在大型应用系统中,这种潜在问题的爆发,往往会引起故障雪崩。 二进制数据所存在的最根本的问题是它的不透明性。不只是对人的不透明,还包括对异构机器的不透明,以及对其他工具的不透明。这种不透明,导致了系统通用性的降低,从而也带来了扩展障碍和维护成本的显著提升。这种似乎躺着都会中枪的系统,最终的宿命只能是被历史所淘汰。 4.2.2 文本的快乐 文本相对于二进制的快乐首先是带给人的,因为人能够直接看懂。这就不需要为文本编写特定的解析工具,人不用去为它付出额外的劳动。毕竟“懒惰”也是人的本性之一。要不然我们发明计算机干嘛,直接掰指头算微积分那该有多“勤快”? 文本的另外一个快乐是带给异构计算机的,因为文本在任何计算机中的存储方式都是相同的。这里所指的文本是以ASCII编码的英文文本。如果你一定要强调中文,那么我就告诉你还有UTF-8这个玩意儿。不管怎样,这些编码在任何计算机中都支持,任何常见的操作系统也都支持,效果是完全一样的。如果一定要找到一个例外,那就是采用MVS系统的IBM大型机,它使用EBCDIC对文本进行编码。不过这东西很多人这辈子都是无缘与它相见的,所以也就别为这点事儿烦心了。 文本的这种对人的直观性和对机器的通用性,非常适合于做配置文件,更适合于传递消息。配置文件因为需要人来编写,使用文本很少会给人带来可配置内容上的理解障碍。使用文本通信,最大的好处就是在调试阶段。人能直观地看到输入了什么内容,输出了什么内容,这期间出了什么问题自然是一目了然了。 使用文本通信还可以为系统的未来省不少力气。最为著名的反例就是采用二进制通信的TCP/IP协议。IPv4的地址是32位的,马上就要用光了。于是就提出了IPv6,将地址扩展到128位。这个变动可不是仅仅修改一下地址长度的问题,几乎整个TCP/IP的架构都进行了重新设计,不但费时费力,还存在推广难题。如果当初TCP/IP采用的是文本通信呢?在地址需要更大的值时,直接写不就行了吗? 当然,文本也不是十全十美没有毛病的。计算机本身不知道文本是个什么东西,要想让它理解一段文本代表什么含义,就需要写程序去解析。这或许是大多数人不容易接受文本(主要是用于通信)的最根本原因。但是,在你还不能证明解析文本所带来的性能损耗就是系统性能瓶颈的时候,或已经证明那就是性能瓶颈但证明不了超过了性能需求预期的时候,就不要改主意。否则就是过早优化。如果你很想设计一个复杂的二进制文件格式,或一个复杂的二进制通信协议时,最好的办法就是去睡上一觉,或许在你醒来的时候这种感觉就过去了。 也正是由于文本的这个无法让计算机直接理解的特性,让你根本不敢去设计内容过于丰富,或结构异常复杂的文本内容,这等于是给自己找麻烦。因此而获得的好处就是程序之间不会互相干涉内部状态,强化了封装。文本的这一点点劣势,因其强化了封装,也成为了一种强劲的优势。 另外一个容易被人诟病的问题就是文本的位密度。但是细想想也不见得比二进制差多少。毕竟它们还是用了八位字节中的7位。而且还有一个不能忽视的事实就是当今世界上的无损压缩算法越来越先进高效。同等信息量的文本和二进制数据经过压缩后,在空间尺度上没有任何差别。如果再能忍受一下压缩算法所导致的效率损失,那么这个问题也就荡然无存了。何况计算机发展的趋势是越来越快的,这点效率上的损失也会因此而在极短的时间内得到弥补。套用一句广告词:用文本,它好我也好*^_^*! 接下来我们再看看Linux是怎样使用文本的。 4.2.3 文本之于配置文件 用Linux的人,应该对/etc/passwd、/etc/group、/etc/inittab等这些配置文件不陌生。它们都是使用文本的良好用例。但是这些配置文件并不是随意编写的,都有一个统一的风格。这个统一的风格在Unix世界称之为DSV风格。 DSV是“Delimiter-Separated Values”的缩写,翻译过来是“分隔符分割值”。翻译的比较绕口,如果你有更好的翻译,请联系我。其实最主要的意思就是使用“分隔符”将一个一个的“值”分割开来,便于取值处理。“值”也可以理解为“字段”。 DSV只是一种风格,并没有规定什么。所以一个DSV风格的文本文件,可以含有多种“分隔符”,也可以用多种方式理解“值”或“字段”。比如这些“值”可以有内置的特定含义,也可以是“键-值”对儿的形式,由“键”来修饰“值”的含义。甚至“值”或“字段”本身也可以是DSV风格的,用来描述更复杂的子项。 比如/etc/passwd就是一个很好的例子。它包含有换行符(n)和冒号“:”这两种分隔符。换行符将文件分割为多个独立的行,每行描述一个用户的账户信息。冒号用于分割账户信息的各种字段。至于“键-值”对儿的例子,恐怕最为著名的就是Windows的ini文件。用等号“=”来分割“键”和“值”,左为“键”右为“值”。虽然Windows与Linux是格格不入的两个东西,但是在这方面还是有些共同认识的。 DSV风格的文件,如果遇到“值”或“字段”中含有“分隔符”的情况,一般建议使用反斜杠“”进行转义。让使用转义方法显得更为强大的是读取这种文件的代码可以通过C风格的转义符嵌入非打印字符数据,进一步扩大了文本的应用范围。 DSV风格的文件能够被绝大多数的传统Unix工具程序所支持,因此它具有极高的通用性。在我们自己编写程序的时候,为了能够利用这种Linux先天就具备的优秀资源,应尽量采用DSV风格来设计我们的文件,无论配置什么。 4.2.4 文本之于程序组合 一个人你让他同时做100件事,可能一件都做不好,或者还没等做到第100件事的时候就累死了。但是,如有100个人,每个人只让他做一件事,估计每个人都会“感谢你八辈祖宗”,兢兢业业地把自己的事情做好。 当你有一种机制,能让只做一件不同于其他人的事情的100个人,进行两两、三三或更多个体的组合,你会发现这100个人可以完成更多的工作。人与人之间最大的障碍就是沟通。如果你选择的机制可以突破沟通问题,那么这100个人,甚至更少的人,就可以高效地完成任何的工作,包括那些你没有预想到的。当然,你可以谈什么成本效益问题。但是如果在你这里累死了人,不用说成本问题,以后是否还能有效益都是一个未知数。 程序也是如此。一个功能繁多的程序不可能比只有一个功能的程序更高效、更稳定;多个不同功能的程序有效地组合在一起,可以展现出比原有功能多得多的功能,以及一些你预想不到的功能;虽然多个程序组合在一起会有一些成本上的担忧,但是获得的效益远比付出的成本要高得多。 在输入输出方面,Unix世界的传统是极力提倡采用简单、文本化、面向流、设备无关的格式,自然也包括Linux。在这些系统下,多数程序都会尽可能的采用简单过滤的形式,即将一个简单的文本输入流过滤成一个简单的文本输出流。尤其是那些最基本的命令程序,有些甚至输入和输出的内容都是相同的。因为不这样做,它们就无法做到任意组合衔接。 文本流之于程序,就如同朴实无华的语言之于人一样,不需用机巧的智慧去理解它深层的含义,不会在沟通上形成障碍。文本流就像前面说的那样,能够加强程序本身的封装性。而那些所谓的精致的、易用的、强大的进程间通信技术,比如远程过程调用所使用的对象序列化,都会导致各程序间内部状态的频繁变化,从而给程序本身带来巨大的复杂性,难于开发和维护。 要想让程序具备良好的组合特性,就要使程序之间彼此独立。作为输入端的程序应该尽可能地不去考虑作为输出端的程序该是什么。让一端的程序能够替换成另一个截然不同的程序,而完全不会惊扰另一端的程序。文本流有其先天的优势,毕竟文本流在字节层面,是没有任何结构可言的。处理的方法只有一种,就是读取它。不管你是从文件、从标准输入,乃至于网络,都是这样。不管想怎么处理,你需要的数据就明显地放在那儿了,怎么解释它们都有道理。即便在最糟糕的情况下,你自己也能看懂它。 4.2.5 文本之于通信协议 既然文本流在程序组合过程中能够发挥巨大优势,那么在基于网络通信的这种另类的程序组合上文本流仍然会是救命良方。 在这方面,要单独从Linux中寻找例子是有些困难的。毕竟到了通信协议这个层面就已经超出操作系统的范围。但那些经典的、历史悠久的、至今仍被广泛应用的通信协议案例,却是无处不在的。这里首推的就是HTTP协议。 HTTP协议就是一种基于文本的通信协议。它采用类似RFC-822/MIME格式的消息来发起或应答客户端的请求。在这种格式中,一个独立的文本行被视作一个消息字段。以冒号“:”后接空格做为分隔符,左侧为字段名,右侧为字段值。字段名不得包含空格,通常用横线“-”代替空格。空行可以解释为消息的结束,也可以解释为接下来是非结构化文本,具体如何解释,由前面的消息属性决定。 HTTP协议已经不仅仅被用于处理Web请求,基本上就是互联网的通用协议。自从万维网在1993年左右吸引到足够量的用户以来,应用协议的设计者们就越来越倾向于在HTTP之上构建自己的专用协议,并使用Web服务器作为通用服务器平台。或许总是有人对这一事实不愤,但是这就是潮流这就是趋势,不信咱们就走着瞧! 4.2.6 硬件也文本 从前面的介绍来看,说Linux已经用文本武装到了牙齿一点都不为过。但是这还没完,它连五脏六腑都没有放过——硬件也文本。 Linux很崇尚的一种设计习惯就是:不管是保存在磁盘中的真实文件,还是用于保存文件的磁盘本身,乃至鼠标、键盘、显示器、声卡、显卡等一切硬件,它都认为是文件,更有甚的是连需要通过网络访问的其他电脑也没放过。会让你有一种:手拿文件句柄,走遍天下都不怕的感觉。虽然不至于所有硬件都能被容易地使用文本来操作,但绝大多数已经是这样了。不信你就打开/sys和/proc目录下面那些代表某个具体硬件设备的文件看看,是不是都是文本的? 这带来几大好处。第一,什么都是文件,程序员就开心死了,不用搞清硬件怎么工作的,直接当普通文件读写就能控制;第二,代表硬件的文件是文本,不用专门的程序就能打开看看它是个什么状态;第三,要修改硬件的特性,可能都不需要写程序了,直接用文本编辑器就能搞定;第四,文本相对于二进制的优点,即便硬件上有很大的变化,反映在文本中可能只是增加了几个字段或几行。 硬件也文本,并不是对文本的滥用。毕竟它给我们带来了实实在在的好处。要做评价,也只能说明文本的强大已经超出了我们的想象。