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でやっていたのだが、ためしにSolarisとCygwinで試したら同じ結果になった。Perlのバージョンは5.8である。Perlの標準出力・標準エラー出力の挙動について、これらのプラットフォームは同じ挙動を示している。