STDOUTとSTDERRをファイルにも出力するようにする

Perlで書いたプログラムを極力変更せずに、STDOUTとSTDERRを通常通り出力させたまま、それに加えてファイルに出力したくなった。そのプログラムはシステム管理的コマンドで、コマンド行から使い、その出力をリアルタイムで見るのだが、出力をファイルにも保存しておきたい。STDERRも記録したいのは、dieしたときなどはSTDERRに出てしまうからである。

そのPerlプログラムの名前をfooとしよう。fooを直接起動するのではなく、以下のようなシェルスクリプト(foowrapperと呼ぼう)から呼び出すことにすれば比較的簡単に目標はほぼ達成できる。

#!/bin/sh
foo 2>&1 | tee file

しかし、これではちょっと不便だ。fooとfoowrapperの両方が必要になる。fooがPATHの中にないといけない。foowrapperの中にfooを絶対パスで書くと、fooの場所を動かしたときに動かなくなる。

少し調べたら以下のような例が出ている。

open(STDOUT, "| tee file");

しかし、STDERRを加えて以下のようにすると、プログラムが終了しなくなる。改行を端末から入力すると終了するが、それは鬱陶しい。

open(STDOUT, "| tee file");
open(STDERR, "| tee -a file");

print "hi\n";
print STDERR "ho\n";

そもそも、teeを2つも起動するのは無駄だ。以下のようにすればteeは1つで済む。

open(STDOUT, "| tee file");
open(STDERR, ">&STDOUT");

print "hi\n";
print STDERR "ho\n";

これでもまだプログラムが終了しないままだ。

プログラムが終了しないのは、ファイルを閉じることができないからだろう。fooが開いているのはSTDIN, STDOUT, STDERRだけだ。試しにSTDOUTを閉じると、事態は悪化した。改行を入力しても終了しなくなった。

open(STDOUT, "| tee file");
open(STDERR, ">&STDOUT");

print "hi\n";
print STDERR "ho\n";
close(STDOUT);

STDERRを閉じて、更にSTDOUTを閉じたら、ちゃんと終了するようになった。

open(STDOUT, "| tee file");
open(STDERR, ">&STDOUT");

print "hi\n";
print STDERR "ho\n";
close(STDERR);
close(STDOUT);

ただし、fooを呼び出した側で標準エラー出力を見ても何も出てこず、すべて標準出力に出てしまう。外から見て標準エラー出力が普通に使われているようにしたまま、ファイルにも記録するのにはもう少し手間がかかりそうだ。

上記のことはRedHat Linuxでやっていたのだが、ためしにSolarisCygwinで試したら同じ結果になった。Perlのバージョンは5.8である。Perlの標準出力・標準エラー出力の挙動について、これらのプラットフォームは同じ挙動を示している。