web-dev-qa-db-ja.com

プログラムの出力をgrepするだけでなく、出力を正常にエコーする方法は?

何か問題が発生したときにエラーメッセージを出力し、それに応じて終了ステータスを設定しないプログラムを使用しています。終了ステータスは常に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で)

6
Wyzard

...プログラムの標準出力および/または標準エラーストリームを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)}'
5
Wildcard

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'
    _
4
Matei David

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
0
John Aspinall

"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を使用します。出力は、grepsがまったく使用されなかったように見えます。

{ 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!
0
agc