何か問題が発生したときにエラーメッセージを出力し、それに応じて終了ステータスを設定しないプログラムを使用しています。終了ステータスは常に0で、成功を示しています。このプログラムをシェルスクリプトから実行し、エラーメッセージが発生した場合にゼロ以外の終了ステータスを取得して、スクリプトがプログラムの失敗を通知できるようにしたいと考えています。
エラーメッセージはgrep
と一致する予測可能なパターンに従い、grep
は一致を検出したかどうかに基づいて終了ステータスを設定するため、プログラムの出力をgrep
と!
で結果を否定して、必要な終了ステータスを取得します。
問題は、grep
がパイプを使用すると、grep
がそれを消費するため、プログラムの出力seeができなくなることです。エラー以外にも重要なメッセージがあるので、出力をスキャンしてエラーメッセージを探しますが、通常どおり出力を表示します。残念ながら、grep
には、一致する行と一致しない行を問わず、すべての入力をパススルーするオプションがありません。
ほとんどが機能することがわかったアプローチの1つは次のとおりです。
! my_program | tee /dev/stderr | grep -q "error message"
これにより、プログラムの出力がgrep
に送られますが、リダイレクトされない標準エラーにもコピーされるため、表示できます。スクリプトの標準出力と標準エラーの両方がターミナルに送られる場合は問題ありません(どちらがメッセージを受け取るかは関係ありません)。ただし、標準出力と標準エラーが別の場所にリダイレクトされると、問題が発生する可能性があります。メッセージは誤った場所に送られます。
bash
またはPOSIXシェル、およびGNUツールを使用して、プログラムの標準出力および/または標準エラーストリームをgrep
およびそれに応じて終了ステータスを設定しますが、両方のストリームを通常の宛先に送信しますか?
(my_program
はエラーメッセージを標準エラーではなく標準出力に書き込むため、標準出力ストリームのみをスキャンできるソリューションは理想的ではありませんが問題ありません。そして、違いが生じる場合は、これはCentOS 7で)
...プログラムの標準出力および/または標準エラーストリームをgrepなどでスキャンし、それに応じて終了ステータスを設定するだけでなく、両方のストリームを通常の宛先に送る(簡潔な)方法はありますか?
... my_programはエラーメッセージを標準エラーではなく標準出力に書き込むため、標準出力ストリームのみをスキャンできるソリューションは問題ありません理想的ではありません。
私の解決策は、上の太字の部分に答えます。
これを行う最も簡単な方法は、awk
を使用することだと思います。
myprogram |
awk 'BEGIN {status = 0} /error message/ {status = 1} 1; END {exit(status)}'
awk
コマンドは、入力したものをすべてそのまま出力しますが、最後に、「エラーメッセージ」が入力の一部であったかどうかに応じたステータスで終了します。
簡潔なバージョン(短い変数名を使用):
myprogram | awk 'BEGIN{s=0} /error message/{s=1} 1; END{exit(s)}'
Stdout/stderrのエラーメッセージ_$MSG
_をgrepし、エラーメッセージが表示されたらすぐにプログラムを停止しないでください。
_# grep on stdout, do not stop early
my_program | awk -v s="$MSG" '$0~s{r=1} 1; END{exit(r)}'
# grep on stdout, do stop early
my_program | awk -v s="$MSG" '$0~s{exit(1)} 1'
# grep on stderr, do not stop early
{ my_program 2>&1 >&3 | awk -v s="$MSG" '$0~s{r=1} 1; END{exit(r)}' >&2; } 3>&1
# grep on stderr, do stop early
{ my_program 2>&1 >&3 | awk -v s="$MSG" '$0~s{exit(1)} 1' >&2; } 3>&1
_
ノート:
すべてのために:grep
を適用するストリームは、_my_program
_がawk
に入るのを見るので、潜在的なttyステータスを失います。これは、_my_program
_がこのストリームに書き込む方法に影響を与える可能性があります。たとえば、カーソル位置を制御できないと想定して回転プログレスバーを印刷しない場合があります。
すべての場合:通常どおり、stdoutとstderrはマージされず、互いに独立してリダイレクトできます。
すべての場合:_my_program
_の元の終了コードは完全に無視されます。 _my_program
_がエラーで終了するか、エラーメッセージが表示された場合にエラーで終了する。この動作を実現するには、パイプラインを実行するBashシェルでpipefail
を有効にする必要があります。例えば。:
_(set -o pipefail; my_program | awk -v s="$MSG" '$0~s{exit(1)} 1')
_
Stderrでのgreppingの場合:上記の単純なコマンド行では、ファイル記述子3が未使用であると想定しています。これは通常、大丈夫な仮定です。この仮定を回避するために、Bashで未使用であることが保証されているファイル記述子を割り当てることができます。
_bash -c 'exec {fd}>&1; { my_program 2>&1 >&${fd} | awk -v s="$MSG" '\''$0~s{exit(1)} 1'\'' >&2; } ${fd}>&1'
_
2つの端末を並べてみてください。最初のシェルウィンドウで:
tail -F /tmp/xyzzy
2番目では、あなたが何をしていたか、tmpファイルに対してのみ:
my_program | tee /tmp/xyzzy | grep -q "error message"
それらをこの順序で開始します。
一時ファイルをときどき消去したり、新しい名前を付けたりするので、それは扱いにくいですが、機能します。
後で追加...次のようなものを試してください:
my_program | tee /tmp/xyzzy ; grep -q "error message" /tmp/xyzzy
シーケンスの終了ステータスは、grepの終了ステータスです。あなたが望んでいるものとは逆です、ため息。だからそれを否定します。
my_program | tee /tmp/xyzzy ; ! grep -q "error message" /tmp/xyzzy
"foo"をstdoutおよびに出力するデモ関数baz()
を作成します「バー」からstderr:
baz() { echo foo ; echo bar >& 2 ; }
単純なケースでは、grep foo
onstdout、およびgrep bar
onstderrを実行します。
{ baz 2>&1 1>&3 | grep bar 1>&2 ; } 3>&1 | grep foo
同じことですが、静かにtee
を使用します。出力は、grep
sがまったく使用されなかったように見えます。
{ baz 2>&1 1>&3 | tee /dev/stderr | grep -q bar ; } 3>&1 | \
{ tee /dev/stderr | grep -q foo ; } 2>&1
次に、grep -q bar
の後に条件文を追加し、「BEEP!」を出力します。 tostderrbarが見つかった場合:
{ baz 2>&1 1>&3 | tee /dev/stderr | grep -q bar && echo "BEEP!" >&2 ; } 3>&1 | \
{ tee /dev/stderr | grep -q foo ; } 2>&1
最後の2行の出力:
foo
bar
BEEP!