web-dev-qa-db-ja.com

注文したSTDOUT / STDERRをキャプチャしてタイムスタンプ/プレフィックスを追加する方法は?

私はほとんど探検してきました allavailablesimilarquestions 、役に​​立たない。

問題を詳しく説明しましょう:

いくつかの無人スクリプトを実行します。これらは標準出力と標準エラー行を生成する可能性があります。それらをキャプチャしたいターミナルエミュレーターによって表示される正確な順序でし、 "STDERR:"と " STDOUT: "彼らに。

私はパイプを使用してみましたが、エポルベースのアプローチでさえ、役に立ちませんでした。解決策はptyの使用法にあると思いますが、私はそれが得意ではありません。 GnomeのVTE のソースコードも調べましたが、それほど生産的ではありませんでした。

理想的には、これを達成するためにBashの代わりに Go を使用しますが、できませんでした。バッファリングのため、パイプが正しい行の順序を維持することを自動的に禁止するようです。

誰かが同様のことをすることができましたか?それとも不可能ですか?端末エミュレーターがそれを行うことができる場合、それは不可能だと思います-おそらく、PTYを異なる方法で処理する小さなCプログラムを作成することによって?

理想的には、非同期入力を使用してこれらの2つのストリーム(STDOUTおよびSTDERR)を読み取り、次に必要に応じて再印刷しますが、入力の順序が重要です!

NOTE:私は stderred を認識していますが、Bashスクリプトでは機能せず、簡単に編集してプレフィックスを追加することはできません(基本的に、多くのシステムコールがラップされているため)。

更新: 2つの要点の下に追加

(1秒未満のランダムな遅延は、一貫した結果の証明のために提供したサンプルスクリプトに追加できます)

更新:この質問の解決策も解決されます この他の質問 、@ Gillesが指摘したように。しかし、私は、あちらこちらで尋ねられたことを行うことが不可能であるという結論に達しました。 2>&1を使用する場合、両方のストリームはpty/pipeレベルで正しくマージされますが、ストリームを個別に正しい順序で使用するには、実際にsyscallフックを含む stderred のアプローチを使用する必要があります。多くの点でdirtyと見なされます。

誰かが上記を反証できる場合は、この質問を更新したいと思っています。

27
Deim0s

コプロセスを使用する場合があります。特定のコマンドの両方の出力を2つのsedインスタンス(stderr用とstdout用)にフィードする単純なラッパー。タグ付けを行います。

#!/bin/bash
exec 3>&1
coproc SEDo ( sed "s/^/STDOUT: /" >&3 )
exec 4>&2-
coproc SEDe ( sed "s/^/STDERR: /" >&4 )
eval $@ 2>&${SEDe[1]} 1>&${SEDo[1]}
eval exec "${SEDo[1]}>&-"
eval exec "${SEDe[1]}>&-"

いくつかのことに注意してください:

  1. それは多くの人々(私を含む)にとって魔法の呪文です-理由があります(以下のリンクされた回答を参照)。

  2. たまに数行をスワップしないという保証はありません-それはすべてコプロセスのスケジューリングに依存します。実際、ある時点でそうなることがほぼ保証されています。つまり、順序を厳密に同じに保つ場合は、stderrstdinの両方からのデータを同じプロセスで処理する必要があります。そうでない場合、カーネルスケジューラは、それ。

    問題を正しく理解している場合は、両方のストリームを1つのプロセスにリダイレクトするようにシェルに指示する必要があることを意味します(これはAFAIKで実行できます)。問題は、そのプロセスが最初に何を行うかを決定し始めたときに始まります。両方のデータソースをポーリングし、ある時点で1つのストリームを処理し、データが両方のストリームに到着してから完了する前の状態になる必要があります。そして、それはまさにそれが故障するところです。また、出力シスコールをstderredのようにラップすることがおそらく目的の結果を得る唯一の方法です(マルチプロセッサシステムで何かがマルチスレッドになると問題が発生する場合もあります)。

コプロセスに関する限り、詳細な洞察を得るために Bashでコマンドcoprocをどのように使用しますか? でステファンの優れた答えを必ず読んでください。

14
peterph

方法#1。ファイル記述子とawkの使用

SO Q&Aタイトル: テキストの行にタイムスタンプを付加するUnixユーティリティはありますか? そしてこれSO Q&Aタイトル: シェルスクリプトの2つの異なるプロセスにSTDOUTとSTDERRをパイプしますか?

アプローチ

ステップ1、呼び出されたときにタイムスタンプメッセージを実行する2つの関数をBashで作成します。

$ msgOut () {  awk '{ print strftime("STDOUT: %Y-%m-%d %H:%M:%S"), $0; fflush(); }'; }
$ msgErr () {  awk '{ print strftime("STDERR: %Y-%m-%d %H:%M:%S"), $0; fflush(); }'; }

ステップ2上記の関数を使用して、目的のメッセージを取得します。

$ { { { ...command/script... } 2>&3; } 2>&3 | msgErr; } 3>&1 1>&2 | msgOut

ここでは、aをSTDOUTに書き込み、10秒間スリープしてから、出力をSTDERRに書き込む例を作成しました。このコマンドシーケンスを上記の構成に挿入すると、指定したとおりにメッセージが送信されます。

$ { { echo a; sleep 10; echo >&2 b; } 2>&3 | \
    msgErr; } 3>&1 1>&2 | msgOut
STDERR: 2014-09-26 09:22:12 a
STDOUT: 2014-09-26 09:22:22 b

方法#2。注釈出力の使用

devscriptsパッケージの一部であるannotate-outputと呼ばれるツールがあり、目的に応じて動作します。唯一の制限は、スクリプトを実行する必要があることです。

上記の例のコマンドシーケンスをmycmds.bashというスクリプトに次のように配置すると、

$ cat mycmds.bash 
#!/bin/bash

echo a
sleep 10
echo >&2 b

その後、次のように実行できます。

$ annotate-output ./mycmds.bash 
09:48:00 I: Started ./mycmds.bash
09:48:00 O: a
09:48:10 E: b
09:48:10 I: Finished with exitcode 0

タイムスタンプ部分の出力形式は制御できますが、それを超えることはできません。しかし、それはあなたが探しているものと同様の出力なので、それは目的に合うかもしれません。

7
slm