画面にstderrのみを表示しますが、stdoutとstderrの両方をファイルに書き込みます


24

これを実現するためにBASHマジックを使用するにはどうすればよいですか?
画面にstderrの出力のみを表示し
たいのですが、stdoutとstderrの両方をファイルに書き込みたいです。

明確化:stdoutとstderrの両方を同じファイルに収めたい。それらが起こる順序で。
残念ながら、以下の答えはどれもこれを行いません。


回答:


14

リダイレクションがなくても、または以外の>logfile 2>&1場合でも、出力が生成順に表示されるとは限りません。

まず第一に、アプリケーションからの標準出力は行バッファリング(ttyに)またはバッファリング(パイプラインに)されますが、stderrはバッファリングされないので、読者に関する限り出力の順序の関係は壊れます。作成できるパイプラインの後続のステージは、2つのストリームへの決定論的に順序付けられたアクセスを取得しません(概念的には並行して発生するものであり、常にスケジューラーの対象になります-読者がすでにスライスを取得している場合、両方のパイプに書かれているため、どちらが最初に来たかはわかりません)。

「発生する順序」は、アプリケーションにのみ実際に知られています。stdout / stderrでの出力の順序付けは、よく知られている-古典的な、おそらく-問題です。


7

構築を使用する場合:stderrリダイレクトの前にstdoutリダイレクトが設定されるため、stderrstdoutの1>stdout.log 2>&1両方がファイルにリダイレクトされます。

あなたはあなたが得ることができる注文反転した場合は標準出力ファイルにリダイレクトし、その後、コピー標準エラー出力をするSTDOUTあなたはそれがためにパイプをすることができますのでtee

$ cat test
#!/bin/sh
echo OUT! >&1
echo ERR! >&2

$ ./test 2>&1 1>stdout.log | tee stderr.log
ERR!

$ cat stdout.log
OUT!

$ cat stderr.log
ERR!

そのため、2つのストリームを同じファイルに記録することはできません。
artistoex

1
@artistoex:tee -a同じファイルで1>両方の出力を合計するために、同じファイルで使用できます。次のようなものです./test 2>&1 1>out+err.log | tee -a out+err.log
。– mmoya

理由はわかりませんが、これではうまくいきwget -O - www.google.deません。(以下のソリューションと比較してください)
-artistoex

4
tee -a何もリダイレクトされなかった場合、コンソールに表示される出力と同じ出力を使用するための信頼性の高い動作は行われません。経由teeする出力は、コマンドから直接出力される出力と比べてわずかに遅れる場合があり、そのためログの後半に表示される場合があります。
ジル 'SO-悪であるのをやめる'

これは私には機能しません、out + err.logは空です。そして、はい、同じファイルにstderrorと標準出力を追加します
アンドレアス

7

bashスクリプトの先頭に次の行を配置することで、これを実現できました。

exec 1>>log 2> >(tee -a log >&2)

これは、stdoutをファイルにリダイレクトされますlog1>>logファイルへのティー標準エラー出力、その後、) (log()2> >(tee -a logとバックstderrにそれを指示>&2)。このように、私は、1つのファイルを取得するlogために、stdoutとstderrの両方を示しており、および標準エラー出力でもあります通常どおり画面に表示されます。

キャッチは、ファイルに追加するときにのみ機能するようだということです。追加しなかった場合、2つのリダイレクトはお互いに重複しているように見え、どちらか一方の出力のみが最後に表示されます。


これを取得してSTDERR行を「変更」してカスタム文字列を含めると、簡単にgrepすることができますか?
IMTheNachoMan

1

エラーストリームを複製して、コンソールとログファイルの両方に表示するようにします。そのためのツールはでありtee、必要なのはエラーストリームにそれを適用することだけです。残念ながら、コマンドのエラーストリームを別のコマンドにパイプするための標準的なシェル構造はないため、少しファイル記述子の再配置が必要です。

{ { echo out; echo err 1>&2; } 2>&1 >&3 | tee /dev/tty; } >log 3>&1
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^   ^^^^^^^^^^^^    ^^^^^^^^^
  command produces output      stdout3                    log
  command produces error       stderr1   dup to terminal  log

また、これは私にはまったく機能しません。最初にstdout行を取得し、次に 'log'ファイルのすべてのstderr行を取得します。例:{{エコーアウト; echo err 1>&2; echo out2; echo err2 1>&2; } 2>&1>&3 | tee / dev / tty; }>ログ3>&1-
アンドレアス

1
@Andreas:ああ、teeここでも遅延が発生します。純粋なシェルソリューションがあるとは思わない。実際、何らかの方法でアプリケーションを変更せずに解決策があるかどうか疑問に思います。
ジル 'SO-悪であるのをやめる'

1

stderrを画面に書き込み、stderrとstdoutの両方をファイルに書き込む-そして、stderrとstdoutの行を、両方が画面に書き込まれた場合と同じ順序で出力する:

困難な問題であることが判明しました。特に、単に画面にそれらを書き込んだ場合に予想される「同じシーケンス」についての部分です。簡単に言うと、それぞれを独自のファイルに書き込み、バックグラウンド処理を行って、各行(各ファイル)に行が生成された正確な時間をマークし、その後、「tail --follow」stderrファイルを画面に表示します。ただし、「stderr」と「stdout」の両方を順番に見るには、2つのファイル(各行に正確な時間のマークが付いている)を一緒にソートします。

コード:

# Set the location of output and the "first name" of the log file(s)
pth=$HOME
ffn=my_log_filename_with_no_extension
date >>$pth/$ffn.out
date >>$pth/$ffn.err
# Start background processes to handle 2 files, by rewriting each one line-by-line as each line is added, putting a label at front of line
tail -f $pth/$ffn.out | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|1|".$_' >>$pth/$ffn.out.txt &
tail -f $pth/$ffn.err | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|2|".$_' >>$pth/$ffn.err.txt &
sleep 1
# Remember the process id of each of 2 background processes
export idout=`ps -ef | grep "tail -f $pth/$ffn.out" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
export iderr=`ps -ef | grep "tail -f $pth/$ffn.err" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
# Run the command, sending stdout to one file, and stderr to a 2nd file
bash mycommand.sh 1>>$pth/$ffn.out 2>>$pth/$ffn.err
# Remember the exit code of the command
myexit=$?
# Kill the two background processes
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"idout"}/'
echo kill $idout
kill $idout
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"iderr"}/'
echo kill $iderr
kill $iderr
date
echo "Exit code: $myexit for '$listname', list item# '$ix', bookcode '$bookcode'"

はい、これは手の込んだようで、4つの出力ファイル(2つは削除可能)になります。これは解決が難しい問題であると思われるため、いくつかのメカニズムが必要でした。

最後に、予想される順序でstdoutとstderrの両方の結果を確認するには、次を実行します。

cat $pth/$ffn.out.txt $pth/$ffn.err.txt | sort

シーケンスが少なくともstdoutとstderrの両方が単に画面に表示されていた場合に、シーケンスが少なくとも非常に近い理由は次のとおりです。

プロセスの進行中に画面にstderrを表示するには、次を使用します。

tail -f $pth/$ffn.out

元の質問が尋ねられてからずっと後にここに到着した誰かを助けてくれることを願っています。


+1のために、私は似たようなアイデアを持っていました(perlを介して両方のストリームをタイムスタンプします)が、実装に苦労していました。それは私のために動作するかどうか(それは本当に出力の順序を保持している場合、すなわち、他のソリューションはここに標準エラー出力と標準出力のタグ付けの間による非同期に物事を少しずらすと、)私が調査します
オリヴィエ・デュラック

0

Letをf実行したいコマンドにしてから、これを

( exec 3>/tmp/log; f 2>&1 1>&3 |tee >(cat)>&3 )

あなたが望むものを与える必要があります。たとえば、次のようにwget -O - www.google.deなります。

( exec 3>/tmp/googlelog; wget -O - www.google.de 2>&1 1>&3 |tee >(cat)>&3 )

1
これは私にはほとんど機能しますが、ログファイルでは最初にすべてのstdout行を取得し、次にすべてのstderror行を取得します。私はそれらが起こると同時に自然な順序でインターリーブされることを望みます。
アンドレアス

0

私がここで読んだことを考えると、私が見ることができる唯一の解決策は、STOUTまたはSTERRにタイムスライス/タイムスタンプを付けて各行を追加することです。日付+%sは数秒になりますが、順序を維持するには時間がかかります。また、ログを何らかの方法でソートする必要があります。私ができる以上の仕事。


0

私はこの正確な要件に苦労しましたが、最終的には簡単な解決策を見つけることができませんでした。私が代わりにやったことはこれでした:

TMPFILE=/tmp/$$.log
myCommand > $TMPFILE 2>&1
[ $? != 0 ] && cat $TMPFILE
date >> myCommand.log
cat $TMPFILE >> myCommand.log
rm -f $TMPFILE

適切なインターリーブされた順序で、stdoutとstderrをログファイルに追加します。(コマンドの終了ステータスによって判断される)エラーが発生した場合、出力全体(stdout および stderr)が再び適切なインターリーブ順序でstdoutに送信されます。私はこれが私のニーズにとって妥当な妥協案であることがわかりました。成長する複数実行ログではなく、単一実行ログファイルが必要な場合は、さらに簡単です。

myCommand > myCommand.log 2>&1
[ $? != 0 ] && cat myCommand.log

0

stderrからcommand-置換tee -a、およびstdoutを同じファイルに追加する:

./script.sh 2> >(tee -a outputfile) >>outputfile

注:正しい順序を確認してください(ただし、stderr-showなし)。「expect」ツールから「unbuffer」コマンドがあります。これは、端末で表示されるように、ttyをシミュレートし、stdout / errを順番に保持します。

unbuffer ./script.sh > outputfile
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.