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的代码:
/** * 简单展示原理,没有错误处理 * 简单展示原理,能用即可 * 这是所谓的“第一个系统” */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int fork_and_exec(char *cmd, int pin, int pout) { int pid = fork(); if (pid == 0) { if (pin != -1) { dup2 (pin, 0); close(pin); } if (pout != -1) { dup2 (pout, 1); close(pout); } //exec太麻烦,索性用system了 //但是必须知道system的原理(fork+exec...) //if (execlp(cmd, NULL) == -1) { system(cmd); //若是exec且执行成功,就不需要exit了 exit(0); } else if(pid > 0) { if (pin != -1) close(pin); if (pout != -1) close(pout); } else { //TODO } return pid; } int execute_cmd(char *cmd, int in) { char *p = cmd; char *start_cmd = cmd; int pipefd[2]; while (*p) { switch (*p) { case '|': *p++ = 0; //创建一个管道 pipe(pipefd); //下面的语句执行后,程序分叉,第一叉在当前捕获的命令,第二 //叉在后面继续解析命令 //将写管道传给当前捕获的命令用于重定向其stdout if (fork_and_exec(start_cmd, in, pipefd[1]) > 0) { //将读管道传给将要被捕获的命令用于重定向其stdin goto call_forward_pipe_chain; } break; default: p++; } } fork_and_exec(start_cmd, in, -1); fflush(stdout); return 0; call_forward_pipe_chain: execute_cmd(p, pipefd[0]); fflush(stdout); return 0; } int main(int argc, char **argv) { while (1) { char cmd[1024]={0}; int len; printf("cmd>>"); fflush(stdout); gets(cmd); len = strlen(cmd); if (!strcmp(cmd, "q")) { fflush(stdout); printf("done\n"); exit(0); } else { execute_cmd(cmd, -1); } } return 0; }
将上述代码编译成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之类的,也是一个合理的命令。
本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1268974