プロセスを開始する必要がありますfoo
。stdout/stderr を通常どおりに表示したいのですがgrep
、 string の stderrを表示したいと思いますbar
。bar
stderr で見つかったら、 foo
kill する必要があります。
これは可能ですか?
私は当初、ストリームのスウィズリングを含むこれを行う方法を書きましたが、あまり良くありませんでした。一部のコメントはそのバージョンに関連しています。気になる方は歴史を調べてみてください。
これを行う方法は次のとおりです。
(PIDFILE=$(mktemp /tmp/foo.XXXXXX) && trap "rm $PIDFILE" 0 \
&& { foo \
2> >(tee >(grep -q bar && kill $(cat $PIDFILE)) >&2) \
& PID=$! && echo $PID >$PIDFILE ; wait $PID || true; })
古き良き悪夢の燃料。ここで何が起こっているのですか?
mktemp
の両方で機能する構文を使用して一時ファイルを作成し、それを呼び出しますPIDFILE
PIDFILE
。foo
ます。これは、前のパイプライン全体ではなく&
バインドするように、複合ステートメントで行われます。foo
foo
プロセス置換にリダイレクトします(これについては後で説明します)。bar
foo
foo
の PID を変数に取り込み、それを という名前のファイルに書き込みPIDFILE
、それを待ちます。これにより、コマンド全体foo
が終了する前に終了するのを待ちます。これは、それが発生したとき|| true
のエラー終了ステータスを破棄しますfoo
。プロセス置換内のコードは次のように機能します。
tee
入力 (の標準エラー) は、の標準出力を標準エラーにfoo
リダイレクトするため、その標準エラーは実際に標準エラーに表示されます。tee
foo
grep -q
に指定されたパターンを探す入力に対して実行され、それが見つかるとすぐに (またはストリームの最後に到達すると) 終了し、何も出力せずに終了します。文字列と正常に終了しました) シェルは続きます ...kill
によって名前が付けられたファイルに取り込まれた PID を持つプロセスPIDFILE
、つまりfoo
トム・アンダーソンの答えは非常に良いですが、それは自分のシステムで終了するか、Ctrl-C を介して終了したkill $(cat $PIDFILE)
場合にのみ発生します。foo
次の解決策は私にとってはうまくいきます
while read g
do
if [[ $g =~ bar ]]
then
kill $!
fi
done < <(
exec foo 2> >(tee /dev/tty)
)
Expect は、プロセスからの出力に基づいてアクションを実行するように設計されています。最も簡単な解決策は、Expect にプロセスを開始させ、期待される出力が表示されたら終了することです。例えば:
expect -c 'set msg {Saw "foo" on stderr. Exiting process.}
spawn /bin/bash -c "echo foo >&2; sleep 10"
expect "foo" { puts $msg; exit }'
生成されたプロセスが正常に終了した場合 (たとえば、"foo" が表示される前)、Expect スクリプトも終了します。
他の答えの代わりとして、1つの方法はbashのcoproc
機能を使用することです。
{coproc FOO { foo; } 2>&1 1>&3; } 3>&1
CHILD=$!
while read line <&${FOO[0]}; do
if echo "$line" | grep -q bar; then
kill $CHILD
else
echo "$line"
fi
done
ただし、これは明らかにbash固有です。
私は実際に、PID ファイルやコルーチンを使用せずに、すべての POSIX 互換シェルで動作するようにこれを行う方法を見つけることができました (私は試してみましたbash
) dash
。少なくとも をサポートするシステムでは/dev/fd/
、それがほとんどすべてのシステムのはずです。
ただし、少し複雑なので、好みに合っているかどうかはわかりません。
( # A
( # B
( /tmp/foo 2>&1 1>&3 & echo $! >&4 ) | # C
( tee /dev/fd/2 | ( grep -q bar && echo fin >&4 ) ) # D and E
) 4>&1 | ( # F
read CHILD
read STATUS
if [ "$STATUS" = fin ]; then
kill $CHILD
fi
)
) 3>&1
ここで使用される多数のサブシェルを説明するには:
の本体はA
、fd 3 に複製された通常の stdout で実行されます。サブシェルB
を実行し、 の stdin にパイプされたF
の stdout を使用します。B
F
fd 4で複製されB
たパイプを使用して実行される本体。A
C
stderr を to からのパイプに接続し、stdout を fd 3 から複製して、実際の foo コマンドを実行しC
ますD
。つまり、グローバル stdout に復元されます。次に、PIDfoo
を fd 4 に書き込みます。つまり、サブシェルF
が標準入力に持つパイプに。
D
stderr に出力されたtee
ものをパイプから受け取ってコマンドを実行します。foo
その出力を/dev/fd/2
(グローバル stderr に表示するため) と subshell に接続されたパイプの両方にコピーしますE
。
E
bar を grep し、見つかった場合はfin
fd 4、つまりF
stdin にあるパイプに書き込みます。grep が見つからずに EOF に遭遇した場合は&&
no が書き込まれていることを確認してください。fin
bar
F
から PID を読み取り、 からC
ターミネータfin
を読み取りますE
。ターミネータが適切に出力された場合fin
、それは を殺しfoo
ます。
編集tee
: foo の stderr を実際の stderr にコピーするための欠落を修正しました。