モダンPerl入門」発売記念ということで、同書を補足するentryを。

同書でちょっと残念だったのが、[5.2 外部コマンドの実行]。あまりモダンではないのだ。

P. 141
system("/sbin/wget", "http://example.com");

これはいいのだが、以下がちょっとまずい。

my $output = `/bin/ls tmp`;
open(my $fh, '| cat -v');

これ、何がまずいか、というと、コマンド実行の際に/bin/shを使ってしまうのだ。そのおかげでcommand < from > to 2&>1のようなリダイレクトも使えるなどの利点もあるのだが、

my $arg    = shift;
my $result = `cmd $arg`;

のようなコードがあったとして、$arg`rm -rf /`が入っていたりすると、期待通りイヤな結末が待っている。幸い同書では変数を渡す例はなかったが、より安全な方法を紹介しておくに超したことはない。しかも、モダンPerlならば手間も増えないのである。

Perl 5.8以前のPerlにおいて、

open(my $fh, '| cat -v');

のより安全な書き方は以下の通りだった。

open my $fh, '|-' or exec qw/cat -v/ or die $!;

偏執狂的にやると、

perldoc perlipc
use English '-no_match_vars';
# add error processing as above
$pid = open(KID_TO_READ, "-|");

if ($pid) {   # parent
    while (<KID_TO_READ>) {
        # do something interesting
    }
    close(KID_TO_READ) || warn "kid exited $?";

} else {      # child
    ($EUID, $EGID) = ($UID, $GID); # suid only
    exec($program, @options, @args)
        || die "can't exec program: $!";
    # NOTREACHED
}

となっていた。しかし、Perl 5.8以降では、以下のようにすっきりと書ける。

open my $fh, '|-', qw/cat -v/ or die $!;

書き込むときには'-|',読み込むときには'|-'を「ファイル名」として指定し、あとは実行したいコマンドと引数をリストとして渡す。shellは全く経由しない。

それでも面倒なら、以下のようにしてもいいだろう。

sub open_pipe2read{
   my $cmd = shift;
   open my $pipe, '-|', $cmd, @_ or die "$cmd: $!";
   $pipe;
}

sub open_pipe2write{
   my $cmd = shift;
   open my $pipe, '|-', $cmd, @_ or die "$cmd: $!";
   $pipe;
}

sub safeqx {
    my $pipe = open_pipe2read @_;
    my @result;
    while ( defined( my $line = <$pipe> ) ) {
        push @result, $line;
    }
    close $pipe or warn "status: $?";
    wantarray ? @result : join( $/, @result );
}

地味なところも、モダンに行きたいものである。

Dan the Modern Perl Monger

See Also: