print文でのSTDOUTの出力先を変更する方法 - すぎゃーんメモの続き。
id:mattnさんから「tieを使うのが一般的かと思いますよ」とコメントをいただきました。ありがとうございます。
tieって名前は聞いたことがあったけど、まったく使ったことがなかった。。
ドキュメント読んだりしながら勉強してみた。
まだよく理解できてないところはあるけど、とにかく「オブジェクトクラスを変数にゴニョゴニョすることができる」らしい。
とりあえず書いてみた。
#!/opt/local/bin/perl use strict; use warnings; my $hoge; { $hoge = tie local *STDOUT, 'Hoge'; print "hoge"; print "fuga"; print "piyo"; } for my $line ($hoge->lines) { print "$line\n"; } package Hoge; sub TIEHANDLE { my $class = shift; my @lines; bless \@lines, $class; } sub PRINT { my $self = shift; for my $arg (@_) { push(@$self, qq['$arg'が書かれたよ]); } } sub lines { my $self = shift; return @$self; }
実行結果:
$ ./tie.pl 'hoge'が書かれたよ 'fuga'が書かれたよ 'piyo'が書かれたよ
- 使用するメソッド(TIEHANDLE, PRINTなど)を実装したpackageを作成する
- tieすると引数に指定したHANDLEがCLASSに結びつけられる
- この場合local *STDOUTがHogeクラスに結びつけられる
- この後、このスコープ内ではSTDOUTへの操作はすべてHogeに結びつけられる
- ので、print文はHoge::PRINTメソッドが呼ばれることになる
- Hoge内では引数をもらって'書かれたよ'と内部変数に保存
- スコープから抜けるとprint文は普通にコンソールに出力される
…というカンジかな?
わかったようなわかってないような。。
まぁ、おそらく実際にこういうのを書くことは少なくて、内部でtieを使っているモジュールを使うことが多いのではないかと。
Tie::STDOUT
use宣言時に、STDOUTに対するそれぞれの操作(print, printfなど)で何を行うかを指定するらしい。
#!/opt/local/bin/perl use strict; use warnings; use Tie::STDOUT print => sub { print join " ", map { $_ x 2 } @_; print "\n"; }; print 'ほげ', 'ふが', 'ぴよ'; syswrite STDOUT, "大事なことなので2回ずつ言いました\n";
$ ./tie_stdout.pl ほげほげ ふがふが ぴよぴよ 大事なことなので2回ずつ言いました
どうすれば元に戻せるのかよく分からなかった。。
IO::Capture::Stdout
これは名前の通り、STDOUTへの出力をキャプチャしてくれる。
#!/opt/local/bin/perl use strict; use warnings; use IO::Capture::Stdout; my $capture = IO::Capture::Stdout->new; $capture->start; print "hoge"; print "fuga"; print "piyo"; $capture->stop; for my $line (reverse $capture->read) { print $line, "\n"; }
$ ./capture_stdout.pl piyo fuga hoge
これは便利だ。readはスカラーコンテキストだと1行ずつ読んでくれるし、リストコンテキストだとまとめて返してくれる。
ソースをみると、start時にtie, stop時にuntieしているのが分かる。
追記
ブコメでIO::Scalarを使った方法もありますと教えていただきました。id:kitsさん、ありがとうございます。
IO::Scalarで色々 - 徒書
標準出力のキャプチャリングはこれを使うのも良さそうだ。
#!/opt/local/bin/perl use strict; use warnings; use IO::Scalar; my $str; { my $fh = new IO::Scalar(\$str); local *STDOUT = $fh; print 'hoge'; print 'fuga'; print 'piyo'; } print "output: $str\n";
$ ./io_scalar.pl output: hogefugapiyo
このようにして、STDOUTへの出力を変数に格納しておくことができる。
もしくは、明示的にtieを書くとこうかな?
#!/opt/local/bin/perl use strict; use warnings; use IO::Scalar; my $str; { tie local *STDOUT, 'IO::Scalar', \$str; print 'hoge'; print 'fuga'; print 'piyo'; } print "output: $str\n";