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
前に送信された場合、grep
stdinを閉じる機会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以外のシステムは、バッファーを無効にしたり定期的にフラッシュしたりするために特別なことをしなければならない場合があります。