「モダン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:

逆だ。