尾部日誌上的多個管道 - 最後一個管道永遠不會收到標準輸入

尾部日誌上的多個管道 - 最後一個管道永遠不會收到標準輸入

我在 Ubuntu 上進入了有趣的狀態。下面的步驟對其進行了最好的描述。

透過一根管道,我看到了我所期望的

# In shell A
tail -f foo.log | grep aaa

# In shell B
echo aaa >> foo.log

# Shell A prints out `aaa`

但是對於多個管道我什麼也看不到

# In shell A
tail -f foo.log | grep aaa | grep bbb

# In shell B
echo aaa bbb >> foo.log

# Nothing ever prints in shell A

但如果我只是迴聲的話,效果很好。

echo 'aaa bbb' | grep aaa | grep bbb

這是我創建最小複製的嘗試——我最初在嘗試從 adb logcat(Android 開發工具)獲取日誌時遇到了問題。我也嘗試過 zsh、bash 和 Fish。

我認為這與我的 inotify 觀察者限制有關,但增加它並沒有改變任何東西。

答案1

這是因為管道中的緩衝,通常不關心行並且可以累積資料。

我認為tail -f單獨使用行緩衝;最後一個grep寫入 tty,因此它也使用行緩衝。因此你的第一個例子是有效的。

grep中間是不同的,您需要透過強制行緩衝或停用緩衝來調整其行為。以下命令將按您的預期工作。

  • 如果您grep支持--line-buffered(Ubuntu 中支持):

      tail -f foo.log | grep --line-buffered aaa | grep bbb
    
  • 更通用的解決方案(它們將與除 之外的許多過濾器一起使用grep):

      tail -f foo.log | unbuffer -p grep aaa | grep bbb
      tail -f foo.log | stdbuf -oL grep aaa | grep bbb
      tail -f foo.log | stdbuf -o0 grep aaa | grep bbb
    

請參閱man 1 grepman 1 unbufferman 1 stdbuf了解詳細資訊和怪癖。

筆記:

  • 這兩種解決方案都不是可移植的(grep --line-bufferedunbufferstdbufPOSIX 未指定)。
  • 如果你能做到這一點,grep --line-buffered那麼它應該是你的選擇。使用額外的工具是沒有意義的。
  • Unix & Linux SE 的相關問題:關閉管道中的緩衝
  • unbufferstdbuf 以完全不同的方式工作
  • 對於stdbuf,行緩衝 ( -oL) 應該優先於無緩衝 ( -o0) ,因為
    • 它很可能表現得更好,
    • 無論如何,管道的其他部分都使用行緩衝。
  • 如果您最後一次grep寫入另一個文件,它的行為會像另一個grep.在這種情況下,如果您希望行立即出現在最終文件中,那麼您也應該修改最後一個grep.
  • 在 中fish,如果grep是包裝函數,則您可能無法使用 獲得所需的行為--line-buffered。使用command grep --line-buffered。看這個問題:輸出管道等待 EOFfish

附註:(tail foo.log | grep aaa | grep bbbtail沒有-f)不會導致問題,因為tail退出。退出時tail,第一個grep偵測到 EOF,刷新其緩衝區並退出,然後第二個grep執行相同操作。

相關內容