Perlのsystem()に@_を渡せなくてハマった。と思ったら単純ミス。

背景

コマンドラインで動くPerlのプログラムをTDDで作ろうと思い、同じくコマンドラインプログラムであるApp::cpanminusのテストの書き方を真似して書いていた。

問題

でもユーティリティ関数で呼んでいるsystem()に引数が渡らない!なぜ!><

純化するとこんなプログラム

#!/usr/bin/env perl

use strict;
use warnings;
use Capture::Tiny qw(capture);

sub caller0 {
    my $out = capture { system('echo', @_) };
    print $out;
}

sub caller1 {
    my @args = @_;
    my $out = capture { system('echo', @args) };
    print $out;
}

print "caller 0\n";
caller0('output!');  # 何も出ない

print "caller 1\n";
caller1('output!');  # ちゃんと出力される

caller0内で直接@_を使うと動かない。引数に何も渡ってこない。

解決編

ひたすらハマったけど気づいてみると当たり前だった。captureは単なる関数なので、{ ... } は無名サブルーチンを渡しているだけ。なので caller0の引数のつもりで@_と書いても、実際はcaptureに渡しているサブルーチンの引数と解釈される。だからcapture呼ぶ前にコピーしてそっちを渡しましょうっていうだけの話。

おまけ

ちなみに参考にしてるApp::cpanminusのコードはこちら

xt/Run.pm

package xt::Run;
use strict;
use base qw(Exporter);
our @EXPORT = qw(run last_build_log);

use Capture::Tiny qw(capture);
use File::Temp qw(tempdir);

$ENV{PERL_CPANM_HOME} = tempdir(CLEANUP => 1);

sub run {
    my @args = @_;
    my @notest = $ENV{NOTEST} ? ("--notest") : ();
    return capture { system($^X, "./script/cpanm.PL", @notest, "--quiet", "--reinstall", @args) };
}

sub last_build_log {
    open my $log, "<", "$ENV{PERL_CPANM_HOME}/latest-build/build.log";
    join '', <$log>;
}

1;

最初これをコピペしてたのに何で書き換えちゃったんだろう。。。まぁ勉強になりました。