9

プロセスを開始する必要がありますfoo。stdout/stderr を通常どおりに表示したいのですがgrep、 string の stderrを表示したいと思いますbarbarstderr で見つかったら、 fookill する必要があります。

これは可能ですか?

4

5 に答える 5

5

私は当初、ストリームのスウィズリングを含むこれを行う方法を書きましたが、あまり良くありませんでした。一部のコメントはそのバージョンに関連しています。気になる方は歴史を調べてみてください。

これを行う方法は次のとおりです。

(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; })

古き良き悪夢の燃料。ここで何が起こっているのですか?

  1. 最も外側の括弧は、全体をサブシェルに入れます。これは、衛生のために、変数の範囲を制限します
  2. GNU と BSDmktempの両方で機能する構文を使用して一時ファイルを作成し、それを呼び出しますPIDFILE
  3. キャッチオール終了トラップ (最も外側のサブシェルが終了したときに実行される) を設定して、 という名前のファイルを削除しますPIDFILE
  4. 実行しfooます。これは、前のパイプライン全体ではなく&バインドするように、複合ステートメントで行われます。foo
  5. の標準エラーを、表示されるのを待ってから強制終了するfooプロセス置換にリダイレクトします(これについては後で説明します)。barfoo
  6. fooの PID を変数に取り込み、それを という名前のファイルに書き込みPIDFILE、それを待ちます。これにより、コマンド全体fooが終了する前に終了するのを待ちます。これは、それが発生したとき|| trueのエラー終了ステータスを破棄しますfoo

プロセス置換内のコードは次のように機能します。

  1. まず、tee入力 (の標準エラー) は、の標準出力を標準エラーにfooリダイレクトするため、その標準エラーは実際に標準エラーに表示されます。teefoo
  2. 入力のコピーをファイルに送信し、別のプロセス置換 (プロセス置換のプロセス置換)に送信します。
  3. より深いプロセス置換内では、最初grep -qに指定されたパターンを探す入力に対して実行され、それが見つかるとすぐに (またはストリームの最後に到達すると) 終了し、何も出力せずに終了します。文字列と正常に終了しました) シェルは続きます ...
  4. killによって名前が付けられたファイルに取り込まれた PID を持つプロセスPIDFILE、つまりfoo
于 2012-12-30T20:13:33.937 に答える
5

トム・アンダーソンの答えは非常に良いですが、それは自分のシステムで終了するか、Ctrl-C を介して終了したkill $(cat $PIDFILE)場合にのみ発生します。foo次の解決策は私にとってはうまくいきます

while read g
do
  if [[ $g =~ bar ]]
  then
    kill $!
  fi
done < <(
  exec foo 2> >(tee /dev/tty)
)
于 2012-12-30T21:26:32.840 に答える
4

Expect を使用して標準エラーを監視する

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 スクリプトも終了します。

于 2012-12-31T22:57:20.843 に答える
2

他の答えの代わりとして、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固有です。

于 2012-12-30T20:56:31.677 に答える
2

私は実際に、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 を使用します。BF

fd 4で複製されBたパイプを使用して実行される本体。A

Cstderr を to からのパイプに接続し、stdout を fd 3 から複製して、実際の foo コマンドを実行しCますD。つまり、グローバル stdout に復元されます。次に、PIDfooを fd 4 に書き込みます。つまり、サブシェルFが標準入力に持つパイプに。

Dstderr に出力されたteeものをパイプから受け取ってコマンドを実行します。fooその出力を/dev/fd/2(グローバル stderr に表示するため) と subshel​​l に接続されたパイプの両方にコピーしますE

Ebar を grep し、見つかった場合はfinfd 4、つまりFstdin にあるパイプに書き込みます。grep が見つからずに EOF に遭遇した場合は&&no が書き込まれていることを確認してください。finbar

Fから PID を読み取り、 からCターミネータfinを読み取りますE。ターミネータが適切に出力された場合fin、それは を殺しfooます。

編集tee: foo の stderr を実際の stderr にコピーするための欠落を修正しました。

于 2012-12-30T21:54:29.500 に答える