末尾のログに複数のパイプがある - 最後のパイプは標準入力を受信しない

末尾のログに複数のパイプがある - 最後のパイプは標準入力を受信しない

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 grepおよびを参照してください。man 1 unbufferman 1 stdbuf

ノート:

  • どちらのソリューションも移植性がありません ( grep --line-bufferedunbufferおよびはstdbufPOSIX で指定されていません)。
  • これができるのであればgrep --line-buffered、それを選択すべきです。追加のツールを使用する意味はありません。
  • Unix および Linux SE に関する関連質問:パイプ内のバッファリングをオフにする
  • unbufferそしてstdbuf 全く異なる方法で働く
  • ではstdbuf、ここでは行バッファリング( )がバッファリングなし( ) -oLよりも優先されるべきである。-o0
    • おそらくパフォーマンスは向上するだろう。
    • パイプの他の部分では、とにかく行バッファリングを使用します。
  • 最後の がgrepさらに別のファイルに書き込む場合、他の と同じように動作しますgrep。このような場合、最終ファイルにすぐに行を表示したい場合は、最後の の動作も変更する必要がありますgrep
  • ではfishgrepがラッパー関数である場合、 では期待どおりの動作が得られない可能性があります--line-buffered。 を使用してくださいcommand grep --line-buffered。次の質問を参照してください:出力パイプはEOFを待機しますfish

補足: tail foo.log | grep aaa | grep bbb(つまり、tailなし-f) は終了するため、問題は発生しませんtail。 がtail終了すると、最初の がgrepEOF を検出し、バッファーをフラッシュして終了し、2 番目の がgrep同じことを行います。

関連情報