天天看點

UNIX指令行管道機制-UNIX哲學

UNIX從來都不是為人機互動而設計的,而是為程式之間的互動而設計的。

用了多年Linux,從起初的羨慕,崇拜,到初學時的不解,混亂,憤怒,後來失望,困惑,...最終發現,如果你真的非要和Windows相比較的話,UNIX的人機互動确實表現不佳,正是這種不佳才導緻了在使用過程中的種種問題,比如憤怒,比如失望...但是當我真正了解了UNIX的設計初衷的時候,這才徹底明白了一些事情的真相。正如Windows擁有那麼多的UI元素以及紛繁惹人眼的絢麗控件以獲得使用者最大的舒适度一樣,UNIX設計了shell管道以獲得程式之間互動的最大舒适度。

        UNIX是以小為美的設計典型,和Windows不屬于一個理念。但是如果你把UNIX的一個指令了解成Windows界面的一個控件的話,或許會好很多,人們會操作(點選,下拉,拖移...)一個控件以獲得一種效果,而UNIX程式也會将輸出重新輸入給另一個程式以擷取一種效果,和Windows不同的是,這個操作一般不需要人的參與,全是程式之間的事情,人的作用往往展現在程式的組織上,程式之間如何來組織,如何來互動,這需要人來告訴UNIX系統-實際上也就是編寫一個腳本。

        是以,越小的程式越好,便于人們去組織它們,這樣它們的(使用代碼行/總體代碼行)這個比值是最高的,也正是是以,這個事實濃縮成了UNIX的另一個哲學:小程式隻做好一件事。其實,我們發現,這條哲學可以從UNIX終極哲學中衍生出來。程式間的互動遠遠沒有人機互動複雜,我們隻需要想一下程式要如何表達自己的功能就可以了,歸結于一點,那就是程式必須要有産出,也就是輸出,而輸入就是其原材料,程式扮演了加工者的角色,也就是一個過濾器,好的程式始終不要自己産生輸出,正如沒有能源輸入,永動機早晚要停一樣,程式的輸出必須由輸入加工而來,而最終的資料來自于人。是以下面的哲學被衍生了出來:

1.程式隻是一個過濾器

2.程式之間的互動就是輸入和輸出

        這種管道到底是怎麼實作的呢?如果看一下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之類的,也是一個合理的指令。

繼續閱讀