相信很多在linux平臺工作的童鞋, 都很熟悉管道符 '|', 通過它, 我們能夠很靈活的將幾種不同的命令協(xié)同起來完成一件任務(wù)
不過這次咱們不來說這些用法, 而是來探討一些更加有意思的, 那就是管道兩邊的數(shù)據(jù)流"實時性"和管道使用的小提示.
其實我們在利用管道的時候, 可能會不經(jīng)意的去想, 我前一個命令的輸出, 是全部處理完再通過管道傳給第二個命令, 還是一邊處理一邊輸出呢? 可能在大家是試驗中或者工作經(jīng)驗中, 應(yīng)該是左邊的命令全部處理完再一次性交給右邊的命令進行處理, 不光是大家, 我在最初接觸管道時, 也曾有這么一個誤會, 因為我們通過現(xiàn)象看到的就是這樣.
但其實只要有簡單了解過管道這工具, 應(yīng)該都不難得出解釋:
管道是兩邊是同時進行, 也就是說, 左邊的命令輸出到管道, 管道的右邊將馬上進行處理.
管道的定義
管道是由內(nèi)核管理的一個緩沖區(qū),相當于我們放入內(nèi)存中的一個紙條。管道的一端連接一個進程的輸出。這個進程會向管道中放入信息。管道的另一端連接一個進程的輸入,這個進程取出被放入管道的信息。一個緩沖區(qū)不需要很大,它被設(shè)計成為環(huán)形的數(shù)據(jù)結(jié)構(gòu),以便管道可以被循環(huán)利用。當管道中沒有信息的話,從管道中讀取的進程會等待,直到另一端的進程放入信息。當管道被放滿信息的時候,嘗試放入信息的進程會堵塞,直到另一端的進程取出信息。當兩個進程都終結(jié)的時候,管道也自動消失。
管道工作流程圖
通過上面的解釋可以看到, 假設(shè) COMMAND1 | COMMAND2, 那么COMMAND1的標準輸出, 將會被綁定到管道的寫端, 而COMMAND2的標準輸入將會綁定到管道的讀端, 所以當COMMAND1一有輸出, 將會馬上通過管道傳給COMMAND2, 我們先來做個實驗驗證下:
#1.pyimporttimeimportsyswhile1:print'1111'time.sleep(3)print'2222'time.sleep(3)
[root@iZ23pynfq19Z~]#python1|cat
在上面的命令, 我們可以猜測下輸出結(jié)果: 究竟是 睡眠6秒之后, 輸出"1111222", 還是輸出 "1111" 睡眠3秒, 再輸出 "2222", 然后再睡眠3秒, 再輸出"1111" 呢? 答案就是: 都不是! what! 這不可能, 大家可以嘗試下, 我們會看到終端沒反應(yīng)了, 為什么呢? 這就要涉及到文件IO的緩沖方式了,關(guān)于文件IO, 可以參考我的另一篇文章:淺談文件描述符1和2, 在最下面的地方提到文件IO的三種緩沖方式:
全緩沖:直到緩沖區(qū)被填滿,才調(diào)用系統(tǒng)I/O函數(shù), (一般是針對文件)
行緩沖: 遇到換行符就輸出(標準輸出)
無緩沖:沒有緩沖區(qū),數(shù)據(jù)會立即讀入或者輸出到外存文件和設(shè)備上(標準錯誤
因為python是默認采用帶緩沖的fputs(參考py27源碼: fileobject.c: PyFile_WriteString函數(shù)),又因為標準輸出被改寫到管道, 所以將會采取全緩沖的方式(shell 命令具體要看實現(xiàn), 因為有些是用不帶緩沖write實現(xiàn),如果不帶緩沖區(qū),會直接寫入管道), 所以將會采取全緩沖的方式, 也就是說, 直到緩沖區(qū)被填滿, 或者手動顯示調(diào)用flush刷入,才能看到輸出.那我們可以將代碼改寫成下面兩種方式吧
#方式1:填滿緩沖區(qū),我這邊大小是4096字節(jié),你們也可以試下這個值,估計都一樣importtimeimportsyswhile1:print'1111'*4096time.sleep(3)print'2222'*4096time.sleep(3)#方式2:手動刷入寫隊列importtimeimportsyswhile1:print'1111'sys.stdout.flush()//因為是標準輸出,所以直接通過sys的接口去flushtime.sleep(3)print'2222'sys.stdout.flush()time.sleep(3)
輸出結(jié)果:
#第一種方式:[root@iZ23pynfq19Z~]#python1|cat1111.....(超多1,刷屏了..)睡眠3秒..2222.....(超多2,刷屏了..)#第二種方式:[root@iZ23pynfq19Z~]#python1|cat1111睡眠3秒..2222睡眠3秒..1111....
在這里我們已經(jīng)能夠得出結(jié)果, 如果像我們以前所想的那樣, 要等到COMMAND1全部執(zhí)行完才一次性輸出給COMMAND2, 那么結(jié)果應(yīng)該是無限堵塞..因為我的程序一直沒有執(zhí)行完..這樣應(yīng)該是不符合老前輩們設(shè)計初衷的, 因為這樣可能會導(dǎo)致管道越來越大..然而管道也是有大小的~ 具體可以去看posix標準, 所以我們得出結(jié)論是: 只要COMMAND1的輸出寫入管道的寫端(不管是緩沖區(qū)滿還是手動flush), COMMAND2都將立刻得到數(shù)據(jù)并且馬上處理.
那么管道兩邊的數(shù)據(jù)流"實時性"討論到就先暫告一段落, 接下來將在這個基礎(chǔ)上繼續(xù)討論:管道使用的小提示.
在開始討論前, 我想先引入一個專業(yè)術(shù)語, 也是我們偶爾會遇到的, 那就是:SIGPIPE或者是一個更加具體的描述:broken pipe (管道破裂)
上面的專業(yè)術(shù)語都是跟管道讀寫規(guī)則息息相關(guān)的, 那咱們來看下 管道的讀寫規(guī)則吧:
當沒有數(shù)據(jù)可讀時
O_NONBLOCK (未設(shè)置):read調(diào)用阻塞,即進程暫停執(zhí)行,一直等到有數(shù)據(jù)來到為止。
O_NONBLOCK ( 設(shè)置 ) :read調(diào)用返回-1,errno值為EAGAIN。
當管道滿的時候
O_NONBLOCK (未設(shè)置):write調(diào)用阻塞,直到有進程讀走數(shù)據(jù)
O_NONBLOCK ( 設(shè)置 ):調(diào)用返回-1,errno值為EAGAIN
如果所有管道寫端對應(yīng)的文件描述符被關(guān)閉,則read返回0
如果所有管道讀端對應(yīng)的文件描述符被關(guān)閉,則write操作會產(chǎn)生信號SIGPIPE
當要寫入的數(shù)據(jù)量不大于PIPE_BUF時,linux將保證寫入的原子性。
當要寫入的數(shù)據(jù)量大于PIPE_BUF時,linux將不再保證寫入的原子性。
在上面我們可以看到, 如果我們收到SIGPIPE信號, 那么一般情況就是讀端被關(guān)閉, 但是寫端卻依舊嘗試寫入
咱們來重現(xiàn)下SIGPIPE
#!/usr/bin/pythonimporttimeimportsyswhile1:time.sleep(10)#手速不夠快的童鞋可以將睡眠時間設(shè)置長點print'1111'sys.stdout.flush()
這次執(zhí)行命令需要考驗手速了, 因為我們要趕在py醒過來之前, 將讀端進程殺掉
python1|cat------------------------#另一個終端[root@iZ23pynfq19Z~]#ps-fe|grep-P'cat|python'root107754074000:05pts/200:00:00python1root107764074000:05pts/200:00:00cat#讀端進程root1083332581000:06pts/000:00:00grep-Pcat|python[root@iZ23pynfq19Z~]#kill10776
輸出結(jié)果
[root@iZ23pynfq19Z~]#python1|catTraceback(mostrecentcalllast):File"1",line6,insys.stdout.flush()IOError:[Errno32]BrokenpipeTerminated
從上圖我們可以驗證兩個點:
當我們殺掉讀端時, 寫端會收到SIGPIPE而默認退出, 管道結(jié)束
當我們殺掉讀端時, 寫端的程序并不會馬上收到SIGPIPE, 相反的, 只有真正寫入管道寫端時才會觸發(fā)這個錯誤
如果寫入一個 讀端已經(jīng)關(guān)閉的管道, 將會收到一個SIGPIPE, 那讀一個寫端已經(jīng)關(guān)閉的管道又會這樣呢?
importtimeimportsys#這次我們不需要死循環(huán),因為我們想要寫端快點關(guān)閉退出time.sleep(5)print'1111'sys.stdout.flush()
#因為我們想要讀端等到足夠長的時間,讓寫端關(guān)閉,所以我們需要利用awk先睡眠10秒[root@iZ23pynfq19Z~]#python1.py|awk'{system("sleep10");print123}'------------------------[root@iZ23pynfq19Z~]#ps-fe|grep-P'awk|python'root117174074000:20pts/200:00:00python1.pyroot117184074000:20pts/200:00:00awk{system("sleep10");print123}root1172132581000:20pts/000:00:00grep-Pawk|python#5秒過后[root@iZ23pynfq19Z~]#ps-fe|grep-P'awk|python'root116854074000:20pts/200:00:00awk{system("sleep10");print123}root1169832581000:20pts/000:00:00grep-Pawk|python#10秒過后[root@iZ23pynfq19Z~]#python1|awk'{system("sleep10");print123}'123
在上面也已經(jīng)證明了上文提到的讀寫規(guī)則: 如果所有管道寫端對應(yīng)的文件描述符被關(guān)閉,將產(chǎn)生EOF結(jié)束標志,read返回0, 程序退出。
總結(jié)
通過上面的理論和實驗, 我們知道在使用管道時, 兩邊命令的數(shù)據(jù)傳輸過程, 以及對管道讀寫規(guī)則有了初步的認識, 希望我們以后在工作時, 再接觸管道時, 能夠更加有把握的去利用這一強大的工具。
-
Linux
+關(guān)注
關(guān)注
87文章
11207瀏覽量
208721 -
管道
+關(guān)注
關(guān)注
3文章
145瀏覽量
17920
原文標題:聊聊 Linux 的匿名管道
文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論