讀取命名管道:tail 還是 cat?
我使用了一個文件描述符
mkfifo fifo
一旦有東西寫入這個管道,我想立即重用它。我應該使用
tail -f fifo
或者
while true; do cat fifo; done
?
他們似乎做同樣的事情,我無法衡量性能的差異。但是,當系統不支持 inotify(例如 Busybox)時,需要將前者
tail -f -s 0 fifo
但這會佔用 100% 的 CPU 使用率(測試一下:
mkfifo fifo && busybox tail -f -s 0 fifo & echo hi>fifo
/ cancel withfg 1
andCtrl``C
)。那麼 while-true-cat 是更可靠的解決方案嗎?
當你這樣做時:
cat fifo
假設還沒有其他程序打開
fifo
寫入,cat
將阻塞open()
系統呼叫。當另一個程序打開文件進行寫入時,管道將被實例化並open()
返回。cat
將read()
循環呼叫並read()
阻塞,直到其他程序將數據寫入管道。
cat
當所有其他寫入程序已將其文件描述符關閉到fifo
. 在哪個點cat
終止並且管道被破壞¹。您需要
cat
再次執行以讀取之後將寫入的內容fifo
(但通過不同的管道實例)。在:
tail -f file
像
cat
,tail
將等待一個程序打開一個文件進行寫入。但是在這裡,由於您沒有-n +1
從一開始就指定要複製的內容,tail
因此需要等到 eof 才能找出最後 10 行是什麼,因此在寫入結束之前您什麼都看不到。之後,
tail
不會關閉它的 fd 到管道,這意味著管道實例不會被破壞,並且仍然會嘗試每秒從管道讀取(在 Linux 上,可以通過使用inotify
和某些版本來避免輪詢) GNUtail
在那裡做)。這read()
將與 eof 一起返回(馬上,這就是為什麼你會看到 100% CPU 的原因-s 0
(這對於 GNUtail
意味著不在read()
s 之間等待而不是等待一秒鐘)),直到某個其他程序再次打開文件進行寫入。相反,您可能想要使用
cat
,但要確保管道實例在實例化後始終存在。為此,在大多數係統上,您可以執行以下操作:cat 0<> fifo # the 0 is needed for recent versions of ksh93 where the # default fd changed from 0 to 1 for the <> operator
cat
的 stdin 將同時為讀取和寫入打開,這意味著cat
永遠不會在其上看到 eof(即使沒有其他程序打開fifo
用於寫入,它也會立即實例化管道)。在這不起作用的系統上,您可以改為:
cat < fifo 3> fifo
這樣,一旦其他程序打開
fifo
寫入,第一個只讀open()
將返回,此時 shell 將open()
在開始之前執行只寫cat
,這將防止管道再次被破壞。所以,總結一下:
- 相比
cat file
,第一輪過後就不會停止。- 相比
tail -n +1 -f file
:它不會read()
在第一輪之後每秒都做無用,管道的一個實例上永遠不會有 eof,當第二個程序在第一個已經關閉它。- 相比
tail -f file
。除了上述之外,它不必等到第一輪結束就可以輸出一些東西(只有最後 10 行)。- 與循環相比
cat file
,只有一個管道實例。¹中提到的比賽視窗將被避免。¹此時,在最後
read()
一個指示 eof 和cat
終止和關閉管道的讀取端之間,實際上有一個小視窗,在此期間程序可以fifo
再次打開寫入(並且不會被阻塞,因為仍有讀取端)。然後,如果它在cat
退出之後和另一個程序打開fifo
讀取之前寫了一些東西,它將被 SIGPIPE 殺死。