Linux 中的管道是什么?管道重定向是如何工作的?

我們在命令行中經(jīng)常會用到類似 cmd0 | cmd1 | cmd2 的寫法。其實,這是管道重定向(pipe redirection),用于將一個命令的輸出作為輸入重定向到下一個命令。
那么,你知道它具體是怎么工作的嗎?今天我們來詳細(xì)了解一下。
注:本文中會有多個地方使用 Unix 這個術(shù)語(而不是Linux),因為管道的概念起源于 Unix。
Linux 中的管道:總體思路
以下是關(guān)于“什么是 Unix 管道?”的內(nèi)容:
Unix 管道是一種 IPC(Inter Process Communication,進(jìn)程間通信)機(jī)制,它將一個程序的輸出轉(zhuǎn)發(fā)到另一程序的輸入。
現(xiàn)在,我們換一種更加專業(yè)且易懂的語言重新解釋一下:
Unix 管道是一種 IPC(Inter Process Communication,進(jìn)程間通信)機(jī)制,它接收程序的標(biāo)準(zhǔn)輸出(stdout),并通過緩沖區(qū)將其轉(zhuǎn)發(fā)給另一個程序的標(biāo)準(zhǔn)輸入(stdin)。
這樣的描述,大家應(yīng)該能理解了。參考下圖可以了解管道的工作原理:

管道命令的最簡單示例之一是將一些命令輸出傳遞給 grep 命令以搜索特定字符串。
比如,我們可以搜索名稱包含txt的文件,如下所示:

管道將標(biāo)準(zhǔn)輸出重定向到標(biāo)準(zhǔn)輸入,但不是作為命令參數(shù)
有個非常重要的一點(diǎn)需要注意,管道命令將標(biāo)準(zhǔn)輸出(stdout)傳遞到另一個命令的標(biāo)準(zhǔn)輸入(stdin),但不是作為參數(shù)。下面我們舉個例子來說明這一點(diǎn)。
如果我們不帶任何參數(shù)使用 cat 命令,它默認(rèn)會從 stdin 讀取內(nèi)容。看下面的例子:
在上面的例子中,沒有帶任何參數(shù)使用了 cat,因此它默認(rèn)會讀取 stdin。接下來,我寫了一行文字,然后按鍵 Ctrl+d 告訴它我寫完了(Ctrl+d 表示 EOF 或文件結(jié)束)。隨后,cat 命令讀取 stdin,然后把之前我寫的那行文字輸出到了終端中。
現(xiàn)在,看如下命令:
管道右邊的命令并不等于 cat hey。這里,標(biāo)準(zhǔn)輸出(stdout)"hey" 被放在了緩沖區(qū)(buffer),并被傳輸?shù)搅?cat 命令的標(biāo)準(zhǔn)輸入(stdin)。由于沒有命令行參數(shù),所以 cat 默認(rèn)讀取 stdin,而 stdin 中恰好有了內(nèi)容(即“hey”),因此 cat 讀取了這個內(nèi)容,并將其打印到 stdout。
為了演示這個區(qū)別,我們可以創(chuàng)建一個名為 hey 的文件,并在其中添加一些文本。參見下圖:

Linux 中的管道類型
Linux 中有兩種類型的管道:
1)匿名管道,也就是未命名管道;
2)命名管道。
匿名管道
顧名思義,匿名管道就是沒有名稱。當(dāng)你使用 | 符號時,它們就會由 Unix shell 動態(tài)創(chuàng)建了。
我們通常所說的管道,就是指的匿名管道。它用起來很方便,作為最終用戶,我們不需要跟蹤它的運(yùn)行,shell 自動會處理這一切。
命名管道
這個稍有不同,命名管道在文件系統(tǒng)中確實存在。它們像普通文件一樣存在,可以使用下面的命令創(chuàng)建命名管道:
這將創(chuàng)建一個名為 pipe 的文件,執(zhí)行以下命令:
請注意開頭的“p”,這意味著該文件是一個管道。現(xiàn)在我們來使用這個管道。
如前所述,管道將命令的輸出轉(zhuǎn)發(fā)給另一個命令的輸入。這就像快遞服務(wù),你把包裹從一個地址送到另一個地址。因此,第一步是提供包裹。
我們會看到 echo 信息沒有打印出來,看起來像是被掛起了。新打開一個終端,嘗試讀取該文件:
我們看下兩個終端的輸出結(jié)果,如下圖所示:

驚訝嗎?這兩個命令同時完成了執(zhí)行。
這是普通文件和命名管道之間的基本區(qū)別之一。在其他進(jìn)程讀取管道之前,不會將任何內(nèi)容寫入管道。
那么,為什么要使用命名管道呢?我們來看一下。
命名管道不會占用磁盤上的任何內(nèi)存。
如果我們執(zhí)行命令 du -s pipe,就會發(fā)現(xiàn)它不會占用任何空間。這是因為命名管道就像從內(nèi)存緩沖區(qū)讀寫的端點(diǎn)。寫入命名管道的任何內(nèi)容實際上都存儲在臨時內(nèi)存緩沖區(qū)中,當(dāng)從另一個進(jìn)程執(zhí)行讀取操作時,該緩沖區(qū)將被刷新。
節(jié)省 IO
因為寫入命名管道意味著將數(shù)據(jù)存儲到內(nèi)存中的緩沖區(qū)中,因此如果涉及大文件的操作的話,就會大幅減少磁盤 I/O。
兩個不同進(jìn)程之間的通信
通過使用命名管道,可以高效地從另一個進(jìn)程實時獲取事件的輸出。因為讀和寫同時發(fā)生,所以沒有等待時間。
較低層次的管道理解(針對高級用戶和開發(fā)人員)
接下來我們更深入的討論一下管道,以及具體的實現(xiàn)。這些需要對以下內(nèi)容有基本的了解:
- C 程序工作原理;
- 什么是系統(tǒng)調(diào)用;
- 什么是進(jìn)程;
- 什么是文件描述符。
我們不會很詳細(xì)的介紹這些概念,只討論與管道相關(guān)的內(nèi)容。對于大多數(shù)Linux用戶來說,下面的內(nèi)容可以選擇性的閱讀。
為了進(jìn)行編譯,在文章最后提供了一個示例 makefile。當(dāng)然,這只是用來說明的偽代碼。
看以下程序:
在第16行,我使用 pipe() 函數(shù)創(chuàng)建了一個匿名管道,傳遞了一個長度為 2 的帶符號整數(shù)數(shù)組。
這是因為管道只是一個包含兩個無符號整數(shù)的數(shù)組,代表兩個文件描述符。一個用于寫,一個用于讀。它們都指向內(nèi)存上的緩沖區(qū)位置,通常為1mb。
這里我將變量命名為fd。fd[0] 是輸入文件描述符,fd[1] 是輸出文件描述符。在該程序中,一個進(jìn)程將字符串寫入 fd[1] 文件描述符,另一個進(jìn)程從 fd[0] 文件描述符讀取。
命名管道也一樣,使用命名管道(而不是兩個文件描述符),你可以從任何一個進(jìn)程中打開一個文件,并像其他文件一樣對其進(jìn)行操作。同時應(yīng)記住管道的特性。
下面是一個示例程序,它執(zhí)行與前一個程序相同的操作,但它創(chuàng)建的不是匿名管道,而是命名管道:
在這里,我使用 mknod 系統(tǒng)調(diào)用來創(chuàng)建命名管道。如你所見,雖然在完成時刪除了管道,但你可以不使用它,只需要打開并寫入本例中的 npipe 文件,就可以輕松的實現(xiàn)在不同進(jìn)程之間的通信。
其實現(xiàn)實中,我們也不必創(chuàng)建兩個管道來實現(xiàn)雙向通信,匿名管道就是這樣的。
以下是一個簡單的 Makefile 的源代碼示例(只是示例),將其與前面的程序放在同一個目錄中(分別為 pipe.c 和 fifo.c)。
以上就是本次分享的關(guān)于 Unix 管道的全部內(nèi)容,歡迎討論。



























