system 関数の出力をファイルにリダイレクト

Perl Cookbook19.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

新しいコメントの投稿

このフィールドの内容は非公開にされ、公表されることはありません。
  • HTMLタグは使用できません
  • 行と段落は自動的に折り返されます。

書式オプションに関するより詳しい情報...

CAPTCHA
この質問はあなたが人間であるかどうかについて調べる為と、自動化したスパムを防ぐ為のものです。うまくいかない場合は電子メールでお問い合わせ下さい。
イメージ CAPTCHA
画像の中に見える文字を入力して下さい