Perl Cookbook の 19.6. Executing Commands Without Shell Escapes によると、system() にはコマンド文字列をリスト形式で渡したほうが安全だとある。私が人に教えるときも、system() を使うときにはリスト形式を使うよう
強く勧めている。
ところが先日、問題が発覚した。system() の中で標準出力をファイルにリダイレクトしたいのだが、リスト形式だとうまくいかないというのだ。
言われて直ぐにちょこっと調べてみたのだが、確かにうまくいかない。
system("ls -l > /tmp/ls.log");
ということを、リスト形式で実現したいと言うのだが。
system("ls", "-l", ">", "/tmp/ls.log");
なんてやり方でやると ls: >: No such file or directory
と言って怒られてしまう。もちろん、これにはちゃんとした理由があって、system にリスト形式の引数を渡すと SHELLを呼び出さないからである。(> を使ったファイルのリダイレクトは SHELLの機能だ。)
とりあえず週末に調べてくるからと言って、時間を貰って調べる事にした。
こういう場合、C言語でプログラミングをしていたときには dup() を使った(かなり昔の事なのに意外と覚えている)。Perl にも同じ関数(dup はシステムコールだが。)があるはずだと調べてみたが、そんなものは無い。さらに詳しく調べてみると open で実現できるようだ。
open(STDOUT, ">/tmp/ls.log");
こうする事で、STDOUT が /tmp/ls.log ファイルへの出力に割り当てられる。C言語ならこんな感じ。
fd = open("/tmp/ls.log", O_WRONLY | O_CREAT, 0644);
dup2(fd, 1);
fork + exec を使って書いてみる
fork が生成する子プロセスの STDOUTを /tmp/ls.log への出力に割り当てる。この方法だと親プロセスには影響を与えない為、親プロセスでの STDOUT は /tmp/ls.log ファイルに出力されない。
$pid = fork();
if ($pid == 0) {
# Child process
open(STDOUT, "> /tmp/ls.log");
exec("ls", "-l");
exit 1;
}
while (wait() != $pid) {
;
}
# スクリプトの続き.....
STDOUTを別のファイルハンドルに残しておく
fork なんか使わなくても、STDOUT を別のファイルハンドルに残しておいて、後から戻す方法でもうまくいく。
open SAVEOUT, ">&STDOUT";
open STDOUT, "> /tmp/test.log";
system("ls", "-l");
close STDOUT;
open STDOUT, ">&SAVEOUT";
# スクリプトの続き.....
うーん。Perl の openは奥が深いね。
googleから来た
system("ls", "-l", ">", "/tmp/ls.log");
あまり詳しくはないのですがjoin使うのはダメなの?
system(join(" ",("ls", "-l", ">", "/tmp/ls.log"));
その他参考になりましたありがとう
Re: system 関数の出力をファイルにリダイレクト
kerotan さん。コメントありがとうございます。
join() を使うと、結局 system("ls -l > /tmp/ls.log")を実行するのと同じで、SHELLが解釈しちゃうんですね。
で、なぜSHELLが解釈するとまずいかと言う話になるんですが、(このエントリではそれを説明してなかったです。すみません。)近日中に別のエントリで説明します。
ちょっと今日はまだ仕事が残っているので。m(_ _)m
新しいコメントの投稿