tieでSTDOUTをつかまえるの記事に対するid:vkgtaroさんのブックーマークコメントで、IO::Scalarを使う方法を教えてもらいました。以下、Log::Dispatch::Colorfulのテストスクリプトより。
my $err; tie *STDERR, 'IO::Scalar', \$err;
先の2記事(tieでSTDOUTをつかまえる、tieでSTDINを置き換える)ではそれぞれ「printだけ」「readline(<>
演算子)だけ」のテストだったので、あのような最小限のクラスでも動いてましたが、様々なI/O操作に対応するのであれば、自分でクラスを作らずにこのIO::Scalarを利用するのがよさそうです。
ちなみにSTDIN/STDOUT を使うテストで使われていたIO::ScalarArrayは、IO::Scalarと同じくIO::stringyに含まれるモジュールでした。よく見ておくべきでした……。
さて、IO::Scalarのperldocを読んでみると「new
でファイルハンドルを返すのであれば、tie
を使わなくてもSTDOUT
等との置き換えはできるのでないか」と思いつき、以下を試してみました。
use strict;
use warnings;
use IO::Scalar;
my $out;
{
local *STDOUT = IO::Scalar->new(\$out);
print "foo\n";
# Can't call method "PRINT" on an undefined value
}
print '$out=', $out;
しかしこれだとうまく動作しません。さらに試してみたところ、以下のように一度スカラー変数への代入を介すると、意図した動作になりました。
use strict;
use warnings;
use IO::Scalar;
my $out;
{
my $fh = IO::Scalar->new(\$out);
local *STDOUT = $fh;
print "foo\n";
}
print '$out=', $out;
# $out=foo
型グロブへの代入とスカラー変数への代入でどう動作が変わってくるのか……自分には分かりませんでした。
以下はSTDIN
との置き換えも含めたサンプルです。
use strict;
use warnings;
use IO::Scalar;
use Test::More qw( no_plan );
# catch stdout test
my @src1 = ('a' .. 'z');
my $dist1;
{
my $fh = IO::Scalar->new(\$dist1);
local *STDOUT = $fh;
print for @src1;
}
is $dist1, join q{}, @src1;
# replace stdin test
my @src2 = map { "$_\n" } ('A' .. 'Z');
my @dist2;
{
my $fh = IO::Scalar->new( \join(q{}, @src2) );
local *STDIN = $fh;
@dist2 = <STDIN>;
}
ok eq_array \@dist2, \@src2;
なお、さらに探してみると、IO::StringというIO::Scalarと同じような動作を行うらしいモジュールもありました。