在shell中我們經常用到管道,有沒考慮過Shell是怎麼實現管道的呢?
cat minicom.log | grep "error"
標準輸入、標準輸出與管道
我們知道,每一個進程都有3個標準的輸入輸出文件描述符
描述符編號 | 簡介 | 作用 |
---|---|---|
0 | 標準輸入 | 通用於獲取輸入的文件描述符 |
1 | 標準輸出 | 通用輸出普通信息的文件描述符 |
2 | 標準錯誤 | 通用輸出錯誤信息的文件描述符 |
我們還知道,系統調用pipe
可以創建無名管道
int pipe(int pipefd[2]);
pipe
的作用是創建無名管道,並創建兩個文件描述符
文件描述符 | 作用 |
---|---|
pipefd[0] | 管道數據出口 |
pipefd[1] | 管道數據入口 |
Shell實現管道的原理
在上文的基礎上,我們再看看Shell如何實現管道的。
Shell中通過fork
+exec
創建子進程來執行命令。如果是含管道的Shell命令,則管道前後的命令分別由不同的進程執行,然後通過管道把兩個進程的標準輸入輸出連接起來,就實現了管道。
例如
grep "error" minicom.log | awk '{print $1}'
這句命名的作用非常簡單,
- 通過
grep
命令在minicom.log中檢索含有error
關鍵字的行 - 通過
awk
命令打印grep
的輸出結果中每一行的第一個字段
在Shell中要實現這樣的效果,有4個步驟:
- 創建pipe
- fork兩個子進程執行grep和awk命令
- 把grep子進程的標準輸出、標準錯誤重定向到管道數據入口
- 把awk子進程的標準輸入重定向到管道數據出口
這樣就實現了Shell管道:grep
把結果輸出到管道,awk
從管道獲取數據
Shell如何用C實現管道
我沒研究過Shell的代碼,但不妨礙我們從功能倒推實現,如果是我,我會怎麼做呢?
int main(int argc, char **argv)
{
while(1) {
int pfds[2];
pid_t cmd1, cmd2;
if ((cmd1 = fork()) < 0) {
...
} else if (cmd1 == 0) { /* child */
/*
* dup2 把 pfds[1](管道數據入口描述符) 複製到 文件描述符1&2
* 實現把cmd1的標準輸出和標準錯誤 輸送到管道
*/
dup2(pfds[1], STDOUT_FILENO);
dup2(pfds[1], STDERR_FILENO);
close(pfds[0]);
close(pfds[1]);
exec(cmd1...);
__exit(127);
}
if ((cmd2 = fork()) < 0) {
...
} else if (cmd2 == 0) { /* child */
/*
* dup2 把 pfds[0](管道數據出口描述符) 複製到 文件描述符0
* 實現cmd2從管道中讀取(cmd1的輸出)數據
*/
dup2(pfds[0], STDIN_FILENO);
close(pfds[0]);
close(pfds[1]);
exec(cmd2...);
__exit(127);
}
close(pfds[0]);
close(pfds[1]);
wait(...);
}
}