tail終了するには、いくつかの方法があります。
不十分なアプローチ:tail別の行を強制的に書き込む
一致を見つけてtail終了した直後に、出力の別の行を強制的に書き込むことができgrepます。これによりtail、が取得され、SIGPIPE終了します。これを行う1つの方法はtail、grep終了後に監視対象のファイルを変更することです。
コードの例を次に示します。
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
この例でcatはgrep、stdoutを閉じるまで終了しtailないためgrep、stdinを閉じる前にパイプに書き込むことができません。 未変更catの標準出力を伝播するために使用されgrepます。
このアプローチは比較的単純ですが、いくつかの欠点があります。
- 場合は
grep閉じは標準入力を閉じる前にstdout、常に競合状態が存在します: grep閉じSTDOUT、トリガーcatトリガー、出口へechoのトリガ、tail出力に行を。この行がgrep前に送信された場合、grepstdinを閉じる機会tailがありましたが、SIGPIPE別の行を書き込むまで取得しません。
- ログファイルへの書き込みアクセスが必要です。
- ログファイルを変更しても問題はありません。
- 別のプロセスと同時に書き込みを行うと、ログファイルが破損する可能性があります(書き込みがインターリーブされ、ログメッセージの途中に改行が表示される場合があります)。
- このアプローチは固有のもの
tailです。他のプログラムでは機能しません。
- 第三のパイプラインステージは、(あなたのようなPOSIXの拡張機能を使用している場合を除き、それは硬質第2パイプラインステージのリターン・コードへのアクセスを取得することができます
bashのPIPESTATUS配列)。この場合、grep常に0を返すため、これは大したことではありませんが、一般に、中間段階は、戻りコードを気にする別のコマンドに置き換えられる可能性があります(たとえば、「サーバー開始」が検出され、 「サーバーの起動に失敗しました」が検出された場合)。
次のアプローチでは、これらの制限を回避します。
より良いアプローチ:パイプラインを避ける
FIFOを使用してパイプラインを完全に回避することができ、grep戻った後も実行を継続できます。例えば:
fifo=/tmp/tmpfifo.$$
mkfifo "${fifo}" || exit 1
tail -f logfile.log >${fifo} &
tailpid=$! # optional
grep -m 1 "Server Started" "${fifo}"
kill "${tailpid}" # optional
rm "${fifo}"
コメントでマークされた行は# optional削除でき、プログラムは引き続き動作します。tail入力の別の行を読み取るか、他のプロセスによって強制終了されるまで、そのまま残ります。
このアプローチの利点は次のとおりです。
- ログファイルを変更する必要はありません
- このアプローチは、他のユーティリティでも機能します
tail
- 競合状態に悩まされない
grep(または使用している代替コマンド)の戻り値を簡単に取得できます。
このアプローチの欠点は、特にFIFOの管理の複雑さです。一時ファイル名を安全に生成する必要があり、ユーザーが途中でCtrl-Cを押しても一時FIFOが削除されるようにする必要があります。スクリプト。これは、トラップを使用して実行できます。
代替アプローチ:メッセージを送信して殺す tail
あなたは得ることができますtailようにそれに信号を送信することにより、出口へのパイプラインステージをSIGTERM。課題は、コード内の同じ場所にある2つのことを確実に知ることです: tailのPIDとgrep終了したかどうか。
のようなパイプラインtail -f ... | grep ...を使用すると、最初のパイプラインステージを簡単に変更して、tailバックグラウンド化tailおよび読み取りを行うことで変数にPID を保存できます$!。また、2番目のパイプラインステージを変更して、終了kill時に実行することも簡単grepです。問題は、パイプラインの2つのステージが(POSIX標準の用語で)別個の「実行環境」で実行されるため、2番目のパイプラインステージが最初のパイプラインステージによって設定された変数を読み取れないことです。シェル変数を使用せずに、2番目のステージが何らかの形でtailのPIDを把握してtail、grep戻るときに強制終了できるようにするか、最初のステージに何らかの方法でgrep戻るときに通知する必要があります。
2番目のステージはのPID pgrepを取得するために使用できますtailが、それは信頼性が低く(間違ったプロセスと一致する可能性があります)、移植性pgrepがありません(POSIX標準で指定されていません)。
最初のステージは、PIDを入力echoすることでパイプを介して2番目のステージにPIDを送信できますが、この文字列はtailの出力と混合されます。2つの逆多重化には、の出力に応じて、複雑なエスケープスキームが必要になる場合がありtailます。
FIFOを使用して、grep終了時に2番目のパイプラインステージから最初のパイプラインステージに通知することができます。その後、最初の段階で殺すことができtailます。コードの例を次に示します。
fifo=/tmp/notifyfifo.$$
mkfifo "${fifo}" || exit 1
{
# run tail in the background so that the shell can
# kill tail when notified that grep has exited
tail -f logfile.log &
# remember tail's PID
tailpid=$!
# wait for notification that grep has exited
read foo <${fifo}
# grep has exited, time to go
kill "${tailpid}"
} | {
grep -m 1 "Server Started"
# notify the first pipeline stage that grep is done
echo >${fifo}
}
# clean up
rm "${fifo}"
このアプローチには、以前のアプローチのすべての長所と短所がありますが、より複雑です。
バッファリングに関する警告
POSIXでは、stdinストリームとstdoutストリームを完全にバッファリングできます。これは、tailの出力がgrep任意の長い時間処理されない可能性があることを意味します。GNUシステムでは問題はないはずです。GNUはすべてのバッファリングを回避するGNUをgrep使用しread()、GNU はstdout tail -fへのfflush()書き込み時に通常の呼び出しを行います。GNU以外のシステムは、バッファーを無効にしたり定期的にフラッシュしたりするために特別なことをしなければならない場合があります。