不管是大家熟悉的bash還是好用的zsh,從Unix遠古時代開始shell就伴隨著我們程序員左右,我們可以執(zhí)行一個命令如ls,cd,cat test.txt,也可以讓一組命令通過管道符連接起來完成更高級的工作,比如說我們搜尋test.txt中包含"hello world"字符串的行我們可以用下面的命令:cat test.txt | grep "hello world".那么這么好用的管道符號的背后是如何實現(xiàn)的呢?下面我就用一個簡單的示例來說明一下.
我們的測試文件的格式如下:
//test.txt
this is line 1 ,data
this is line 2 ,data
this is line 3 ,data
this is line 4 ,data
this is line 5 ,data
this is line 6 ,data
this is line 7 ,data
this is line 8 ,data
this is line 9 ,data
this is line 10 ,data
...(共有100行)...
我們寫個簡單的小程序來說明一下管道符的內在原理:
下面的例子模擬在shell中執(zhí)行cat test.txt|grep 96,為了看起來簡潔明了一些錯誤處理就沒寫了.
/*探索shell如何在兩個命令之間建立管道*/
/*過程是先exec p1,然后exec p2,將其用管道聯(lián)系在一起*/
/*注意只有所有的寫端文件描述符都關閉才會使得read讀到EOF*/
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <stdio.h>
int main(){
pid_t pid_p1,pid_p2;
int pipe_fds[2];
if(pipe(pipe_fds) != 0)
{
exit(1);
}
if((pid_p2 = fork()) == 0)// 子進程1
{
dup2(pipe_fds[0],STDIN_FILENO);// 將標準輸入重定向到管道的讀取端
close(pipe_fds[0]);
close(pipe_fds[1]); // 一定要關閉,因為已經(jīng)dup過了,否則read的時候不會知道是否已經(jīng)讀完
execlp("grep","grep","96",(char*)0);
}
else if(pid_p2 < 0)
{
exit(1);// 生成進程失敗
}
if((pid_p1 = fork()) == 0) // 子進程2
{
dup2(pipe_fds[1],STDOUT_FILENO);//將標準輸出重定向到管道寫入端
close(pipe_fds[0]);
close(pipe_fds[1]); // 一定要關閉,因為已經(jīng)dup過了,否則read的時候不會知道是否已經(jīng)讀完
execlp("cat","cat","test.txt",(char*)0); // exec "cat test.txt"
}
else if(pid_p1 < 0)
{
exit(1);// 生成進程失敗
}
// 主進程
close(pipe_fds[0]);
close(pipe_fds[1]); // 一定要關閉,主進程不需要管道
while(wait(NULL)>0); // wait for all processes.
return 0;
}
最主要的調用就是pip()和dup2()了,主要解釋下dup2()的目的在于重定向標準輸入輸出,其他的部分代碼注釋也解釋的比較清楚,就不贅述了,更多參考資料請見<Unix環(huán)境高級編程>的io章節(jié),文件章節(jié),進程部分乃至偽終端部分.
如果從頭說來那就是shell解析用戶敲入的命令,明白用戶要啟動cat 程序和grep程序,并且將cat程序的標準輸出通過pipe管道重定向到grep的標準輸入之中,shell解析出用戶的意圖之后,會調用類似上面的例程,從而將這個命令組合執(zhí)行完畢.