15 December 2012

tee というコマンドがあります。 「標準入力から読んだ内容を標準出力とファイルに書きだす」というものです。

例えば、

$ foo | tee logfile

とすると、 foo コマンドの標準出力は、 logfile に書き出されます。 なおかつ、標準出力は標準出力として、標準エラー出力は標準エラー出力として出力されます。 ログを記録するときには便利なコマンドですね。

そこで思ったのは、 tee コマンドの標準エラー版に相当するものを実現できないかと思いました。 コンパイルのときの warning とかは通常、標準エラー出力へ出てきます。 標準エラー出力をファイルに記録しておいて、 warning を潰していくのに使いたいのです。

で、考えたシェルスクリプトが以下です。

#!/bin/sh

exec 3>&1
foo 2>&1 >&3 3>&- | tee logfile >&2

foo コマンドの標準エラー出力を logfile に書き出します。 なおかつ、標準出力は標準出力として、標準エラー出力は標準エラー出力として出力されます。

m>&n は 「n 番の出力先と同じものを m 番へコピーする (dup2 する)」という意味です。

m>&- は 「m 番を閉じる」という意味です。

また、 exec は引数にコマンドが与えられていない場合、リダイレクト処理はカレントシェルで効果を表します。

つまり、上記のスクリプトは、 foo コマンドの標準出力と標準エラー出力を入れ替えて、 パイプに流し、さらに tee コマンドの標準出力(= foo コマンドの標準エラー出力) を標準エラー出力に戻す、ということをしています。

(後日、bash なら foo |& tee logfile >&2 のように簡単に書けることを知った。。)

さらに、 foo が異常終了した場合に、即座にシェルスクリプトを止めたいときは、以下のようにします。 パイプに渡してしまうと、そのままでは終了ステータスを取れませんので、ちょっと複雑になっております。。

#!/bin/sh

exec 3>&1

status=$({ { foo 2>&1 >&3 3>&- 4>&-; echo $? 1>&4 3>&- 4>&-;} | tee log >&2 3>&- 4>&- ;} 4>&1)

if [ "$status" != "0" ]; then
        set -e
        /bin/false
fi

bash の $PIPESTATUS の実現方法はこちらのページで勉強させていただきました。



blog comments powered by Disqus