bashスクリプトで、stdoutを行ごとにキャプチャする方法


26

bashスクリプトでは、長いコマンドの標準出力を1行ずつキャプチャして、初期コマンドの実行中にそれらを分析および報告できるようにします。これは私がそれを行うことを想像できる複雑な方法です:

# Start long command in a separated process and redirect stdout to temp file
longcommand > /tmp/tmp$$.out &

#loop until process completes
ps cax | grep longcommand > /dev/null
while [ $? -eq 0 ]
do
    #capture the last lines in temp file and determine if there is new content to analyse
    tail /tmp/tmp$$.out

    # ...

    sleep 1 s  # sleep in order not to clog cpu

    ps cax | grep longcommand > /dev/null
done

もっと簡単な方法があるかどうか知りたいです。

編集:

私の質問を明確にするために、これを追加します。longcommand1秒に1回のラインで、そのステータス行を表示します。longcommand完了する前に出力をキャッチしたいと思います。

このように、longcommand期待した結果が得られない場合は、潜在的に殺すことができます。

私が試してみました:

longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done

ただし、whatever(例echolongcommand完了後にのみ実行されます。


回答:


32

コマンドをwhileループにパイプするだけです。これには多くのニュアンスがありますが、基本的には(bashまたはPOSIXシェルで):

longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done

これに関する他の主な落とし穴(IFS以下のものを除く)は、ループが終了したらループ内から変数を使用しようとする場合です。これは、変数にアクセスできないサブシェル(別のシェルプロセス)で実際にループが実行されるためです(また、ループが完了すると変数が完全になくなった時点で終了します)。できるよ:

longcommand | {
  while IFS= read -r line
  do
    whatever "$line"
    lastline="$line"
  done

  # This won't work without the braces.
  echo "The last line was: $lastline"
}

設定のHaukeの例lastpipeでは、bash別のソリューションです。

更新

コマンドの出力を「実際に」処理していることを確認するには、を使用stdbufしてプロセスstdoutを行バッファリングするように設定します。

stdbuf -oL longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done

これにより、出力をブロックに内部バッファリングする代わりに、パイプに一度に1行を書き込むようにプロセスが構成されます。プログラムはこの設定自体を内部で変更できることに注意してください。unbuffer(の一部expect)またはで同様の効果を実現できますscript

stdbufGNUおよびFreeBSDシステムで利用可能です。stdioバッファリングにのみ影響し、動的にリンクされた非setuid、非setgidアプリケーションでのみ動作します(LD_PRELOADトリックを使用するため)。


@Stephane IFS=はには必要ありませんがbash、私は前回からこれをチェックしました。
グレアム14

2
はい、そうです。省略lineした場合は必要ありません(この場合、結果は$REPLY先頭と末尾のスペースを削除せずに入力されます)。試してみてくださいecho ' x ' | bash -c 'read line; echo "[$line]"'とで比較するecho ' x ' | bash -c 'IFS= read line; echo "[$line]"'echo ' x ' | bash -c 'read; echo "[$REPLY]"'
ステファンChazelas

@Stephane、わかりました、それと名前付き変数の間に違いがあることに気づきませんでした。ありがとう。
グレアム14

@Graeme私の質問では明確ではなかったかもしれませんが、longcommandが完了する前に出力コマンドを1行ずつ処理したいと思います(longcommandがエラーメッセージを表示した場合に迅速に対応するため)。私はより明確にするために、私の質問を編集します
gfrigon

@gfrigon、更新。
グレアム14

2
#! /bin/bash
set +m # disable job control in order to allow lastpipe
shopt -s lastpipe
longcommand |
  while IFS= read -r line; do lines[i]="$line"; ((i++)); done
echo "${lines[1]}" # second line
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.