行程間通訊

shell 管線(shell pipeline)

你幾乎一定打過類似 cat log.txt | grep error | wc -l 的指令。豎線把一個個小程式串成一條生產線:第一個命令的輸出餵給第二個的輸入,其輸出又餵給第三個。每個程式做一件事,把結果往下傳。這就是 shell 管線,也是行程間通訊在日常中最常被看見的用法。

在那一行背後,shell 實際做的事是這樣。對每個 | 它呼叫 pipe() 造一條匿名管線。接著為每個命令 fork() 一個子行程。在每個子行程裡,於 exec() 真正的程式之前,它用 dup2() 把該子行程的標準輸出(檔案描述符 1)指向某條管線的寫入端、把標準輸入(檔案描述符 0)指向上一條管線的讀取端——這就是重導向。所以 grep 根本不知道自己身處管線之中;它只是照常對 fd 0 做 read()、對 fd 1 做 write(),彷彿在跟終端機講話。核心的管線緩衝區負責在它們之間搬運位元組。所有命令是並行執行的,不是一個做完下一個才開始;資料一邊產生一邊流過。

這個設計是 Unix 哲學的核心:打造各自把一件事做好、讀寫純文字串流的小工具,再用管線把它們組合起來。管線免費送你並行、串流(一個 10 GB 的檔案可以流過,從不需要整個塞進記憶體)與天然背壓(若 wc 很慢,grep 的寫入會阻塞,進而拖慢 cat)。誠實的提醒:因為管線沒有訊息邊界,鏈上每個工具都必須對某種文字格式(通常是「行」)有共識;而中間發生的錯誤很容易被忽略——預設情況下 shell 只回報最後一個命令的結束狀態,除非你啟用 pipefail。

$ ps aux | grep nginx | wc -l ——shell 造兩條管線、fork 三個子行程(ps、grep、wc),把 ps 的 stdout 接到 grep 的 stdin、grep 的 stdout 接到 wc 的 stdin,再把三者都 exec;它們同時執行。

pipe() + fork() + dup2() + exec(),每一段做一次。你打的那個 | 就是這四件事,只是被藏起來了。

這些命令是並行執行的——沒有暫存檔保存整份中間輸出。這正是管線能處理遠大於記憶體的資料的原因,但也正因如此,管線中途崩潰可能悄悄丟失最後一個命令的結束狀態。

又称
command pipelinepipe chain命令管線