バックグラウンドプロセスの部分的な出力をパイプし、変数に格納します

Aug 23 2020

バックグラウンド:

サーバーがランダムに使用可能なポートで起動されるプロジェクトに取り組んでいます。サーバーは次のコマンドで起動されます。

node server.js

サーバーの準備が整い、使用可能なポートでのサービスを開始すると、サーバーは次の形式でstdoutに完全なサーバーURL(ポート番号を含む)を(他のログとともに)出力します。

Server started! Listening on URL: http://localhost:12927

このサーバープロセスは、手動で強制終了するまで実行を続けます(そして、他のログを出力し続けます)。

実際の問題:

サーバーを起動してその出力をリッスンするスクリプトを書いています。のようなメッセージが表示されたらすぐに、"Server started! Listening on URL: [SERVER_URL]"これSERVER_URLを変数に格納し、スクリプトの後半で処理します(たとえば、自動化テストのためにブラウザーを開きます)。

私が試したこと:

Bashの非常に基本的な理解しか持っていないので、次のオプションを試しました(もちろん、SEについて調査した後)。

  1. grep正規表現での使用について少し調べました。これは、手動で行う場合(つまり、変数に格納せず、echoコマンドからパイプするだけの場合)に非常にうまく機能します。

    SERVER_URL=$(node server.js | grep -m 1 -ohP 'Server started! Listening on URL: \K([^\s$]*)')
    

ただし、この行ではブロックが発生します。おそらく、ノードサーバーが強制終了されるのを待っています。私の要件は明らかです。サーバーの出力からSERVER_URLを、stdoutでそのURLを吐き出すポイントを超えて待たずに入力したいのです。

  1. また、リダイレクトとプロセス置換を少しいじりました。パイプは実際にはソースプロセスが完了するのを待っていることを学びました。リダイレクトを使用して次のことを実行しようとしましたが、機能しません。

    SERVER_URL=$(node server.js >&3 | grep -m 1 -ohP 'Server started! Listening on URL: \K([^\s$]*)')
    

奇妙な「未処理の「エラー」イベント」例外がスローされます(エラー:EPIPEの書き込み)。

どんな助けでも大歓迎です。ありがとう!

回答

3 StéphaneChazelas Aug 23 2020 at 20:55

はい、で:

var=$(cmd | grep -m1 pattern)

cmdのstdoutはへのパイプgrepであり、grep'sstdoutはシェルへのパイプです。

grep最初の一致を見つけた後、それを標準出力に出力して終了します。

シェルはgrepの出力を読み取ります。grep終了時にファイルの終わりを参照してください。ただし、の場合bash、シェルもcmd終了を待機します。

grepが終了したため、cmdのstdoutは壊れたパイプになりました。したがって、次にcmdstdoutに書き込むときは、SIGPIPE信号を受信して​​停止します。

ここで問題となるのは、cmdパターンに一致する最初の行まで読んだら、残りの出力をどうするかということです。

興味がない場合は、それcatを読み取って破棄するバックグラウンドコマンドを実行できます。

{
   url=$(grep -m1 -oP 'Server started! Listening on URL: \K\S+')
   cat <&0 > /dev/null & # <&0 to work around the fact that bash redirects
                         # background commands stdin from /dev/null
} < <(node server.js)

それを保持したい場合は、それをいくつかのファイルにリダイレクトしtail -f、たとえばシェルで使用してURLメッセージを待つことができます。

node server.js > server.log &
{
  IFS= read -r pid
  url=$(grep -m1 -oP 'Server started! Listening on URL: \K\S+') kill -- "$pid"
} < <(echo "$BASHPID"; exec tail -n +1 -f server.log)

ここでは、メッセージが見つかった後にテールのpidを渡して、他に何も出力しtailない場合tailは発生しない可能性のあるSIGPIPEによって強制終了されるのを待つのではありません。