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之類的,也是一個合理的指令。