UNIX命令行管道机制-UNIX哲学

UNIX从来都不是为人机交互而设计的,而是为程序之间的交互而设计的。

用了多年Linux,从起初的羡慕,崇拜,到初学时的不解,混乱,愤怒,后来失望,困惑,...最终发现,如果你真的非要和Windows相比较的话,UNIX的人机交互确实表现不佳,正是这种不佳才导致了在使用过程中的种种问题,比如愤怒,比如失望...但是当我真正理解了UNIX的设计初衷的时候,这才彻底明白了一些事情的真相。正如Windows拥有那么多的UI元素以及纷繁惹人眼的绚丽控件以获得使用者最大的舒适度一样,UNIX设计了shell管道以获得程序之间交互的最大舒适度。
        UNIX是以小为美的设计典型,和Windows不属于一个理念。但是如果你把UNIX的一个命令理解成Windows界面的一个控件的话,或许会好很多,人们会操作(点击,下拉,拖移...)一个控件以获得一种效果,而UNIX程序也会将输出重新输入给另一个程序以获取一种效果,和Windows不同的是,这个操作一般不需要人的参与,全是程序之间的事情,人的作用往往体现在程序的组织上,程序之间如何来组织,如何来交互,这需要人来告诉UNIX系统-实际上也就是编写一个脚本。
        因此,越小的程序越好,便于人们去组织它们,这样它们的(使用代码行/总体代码行)这个比值是最高的,也正是因此,这个事实浓缩成了UNIX的另一个哲学:小程序只做好一件事。其实,我们发现,这条哲学可以从UNIX终极哲学中衍生出来。程序间的交互远远没有人机交互复杂,我们只需要想一下程序要如何表达自己的功能就可以了,归结于一点,那就是程序必须要有产出,也就是输出,而输入就是其原材料,程序扮演了加工者的角色,也就是一个过滤器,好的程序始终不要自己产生输出,正如没有能源输入,永动机早晚要停一样,程序的输出必须由输入加工而来,而最终的数据来自于人。因此下面的哲学被衍生了出来:
1.程序只是一个过滤器
2.程序之间的交互就是输入和输出

因此管道的概念就被呼出来了!什么是管道?管道就是一个pipe(??!fk!),将一个程序的输出和另一个程序的输入联系起来的一根管子。UNIX的命令行管道如下图所示

将各个程序画成阶梯状完全是为了展示管道,实际上它们都是同时执行的,只是启动先后顺序不同而已,一个命令行可以通过“|”来分隔为多个命令,前面命令的输出作为紧接着后面命令的输入,通过管道彼此相连接。

        这种管道到底是怎么实现的呢?如果看一下shell的源代码,比如csh,bash等,那固然不错,可是却容易被太多的额外处理混淆了视听,你很难从复杂的shell源代码中抽出哪些是和命令行管道相关的,加上代码本身的调用层次很深,看懂代码就更佳困难,除非你专门想研究一下shell的实现,否则如果仅仅想知道命令行管道这么一件简单的事情的话,还是自己实现一个为好。这难道不矛盾吗?你都不知道怎么实现的,怎么自己实现啊?!可是你知道效果啊,你也知道规范,这些就够了,这里又有一个不成文的规则:当你只知道效果和规范的时候,自己动手实现一个符合规范的机制之后,你就会明白该机制是怎么实现的了。
        因此,代码不重要,重要的是设计本身!而设计是属于比较高层面的概念,其下是复杂的逻辑,因此要问程序是什么,程序就是逻辑。
        以下就是自己粗略实现的一个执行命令行管道程序的小shell的代码:
  1. /**  
  2.  *    简单展示原理,没有错误处理  
  3.  *    简单展示原理,能用即可  
  4.  *    这是所谓的“第一个系统”  
  5.  */  
  6. #include   
  7. #include   
  8. #include   
  9. #include   
  10.   
  11. int fork_and_exec(char *cmd, int pin, int pout)  
  12. {  
  13.     int pid = fork();  
  14.     if (pid == 0) {  
  15.         if (pin != -1) {  
  16.             dup2 (pin, 0);  
  17.             close(pin);  
  18.         }  
  19.         if (pout != -1) {  
  20.             dup2 (pout, 1);  
  21.             close(pout);  
  22.         }  
  23.         //exec太麻烦,索性用system了  
  24.         //但是必须知道system的原理(fork+exec...)  
  25.         //if (execlp(cmd, NULL) == -1) {  
  26.         system(cmd); //若是exec且执行成功,就不需要exit了  
  27.         exit(0);  
  28.     } else if(pid > 0) {  
  29.         if (pin != -1)  
  30.             close(pin);  
  31.         if (pout != -1)  
  32.             close(pout);  
  33.     } else {  
  34.         //TODO  
  35.     }  
  36.     return pid;  
  37. }  
  38.   
  39. int execute_cmd(char *cmd, int in)  
  40. {  
  41.     char *p = cmd;  
  42.     char *start_cmd = cmd;  
  43.     int pipefd[2];  
  44.     while (*p) {  
  45.         switch (*p) {  
  46.             case '|':  
  47.                 *p++ = 0;  
  48.                 //创建一个管道  
  49.                 pipe(pipefd);  
  50.                 //下面的语句执行后,程序分叉,第一叉在当前捕获的命令,第二  
  51.                 //叉在后面继续解析命令  
  52.                 //将写管道传给当前捕获的命令用于重定向其stdout  
  53.                 if (fork_and_exec(start_cmd, in, pipefd[1]) > 0) {  
  54.                     //将读管道传给将要被捕获的命令用于重定向其stdin  
  55.                     goto call_forward_pipe_chain;  
  56.                 }  
  57.                 break;  
  58.             default:  
  59.                 p++;  
  60.         }  
  61.     }  
  62.     fork_and_exec(start_cmd, in, -1);  
  63.     fflush(stdout);  
  64.     return 0;  
  65. call_forward_pipe_chain:  
  66.     execute_cmd(p, pipefd[0]);  
  67.     fflush(stdout);  
  68.     return 0;  
  69. }  
  70.   
  71. int main(int argc, char **argv)  
  72. {  
  73.     while (1) {  
  74.         char cmd[1024]={0};  
  75.         int len;  
  76.         printf("cmd>>");  
  77.         fflush(stdout);  
  78.         gets(cmd);  
  79.         len = strlen(cmd);  
  80.         if (!strcmp(cmd, "q")) {  
  81.             fflush(stdout);  
  82.             printf("done\n");  
  83.             exit(0);  
  84.         } else {  
  85.             execute_cmd(cmd, -1);  
  86.         }  
  87.     }  
  88.     return 0;  
  89. }  

将上述代码编译成mysh之后,执行之:
root@zhaoya-home:~/test# ./mysh
cmd>>
cmd>>ls /|grep etc
cmd>>etc
cmd>>ls /dev/|grep tty|wc -l
cmd>>69
cmd>>q
done
root@zhaoya-home:~/test#

以上就是命令行管道的效果。通过使用这种管道的粘合-纵向-加上shell脚本逻辑上的粘合-横向,小巧的UNIX命令就可以结合在一起,完成几乎所有的工作,并且代码利用率极高,高得不可想象啊!如果是一个包罗万象的大程序,每一次你只使用其5%的功能,那么95%的代码将在此次执行中浪费掉,然而小程序互相结合就比较好,由于每个小程序仅仅完成一个功能,因此你只将用到的命令结合在一起即可,提升了代码利用率(UNIX是从小内存时代一路走来的,如今内存都低于白菜价了,却依然勤俭)。
        因此UNIX本质上就是通过粘合小程序而工作的,你可以看到,就算true,false之类的,也是一个合理的命令。


请使用浏览器的分享功能分享到微信等