概要
結論から言えば、-iオプションは<>ループ外から使おうとしても狙い通りの挙動にならない。よってワンライナーでは原則として-nや-pとともに使うべきである。
例えば、以下は期待した挙動にならない。(perl 5.18.2で確認。以下同様)
## 3行目を削除して出力したい
$ perl -i.orig -e '@f = <>; $f[2] = ""; print @f' sample.txt
この場合、出力結果は標準出力に出てしまい、sample.txtは空ファイルになる。
以下も同様である。
## 各行に"> "という接頭辞をつけたい
$ perl -i.orig -e 'print "> $_" for <>' sample.txt
なぜそうなるのか?
perlrun, perlvarによれば、グローバル変数$ARGVには現在<>で読み込んでいるファイル名が、グローバルファイルハンドルARGVOUTには-iオプションの効果による出力先ファイルハンドルがセットされるらしい。
そこで、以下のスクリプトを用意する。
sub p {
my ($dev, $ino) = stat(*ARGVOUT);
print STDERR "ARGV: $ARGV, ARGVOUT: $ino, ^I: $^I\n";
}
print STDERR "-- before loop\n";
p();
print STDERR "-- enter loop\n";
while(<>) { p() }
print STDERR "-- after loop\n";
p();
で、実行。
$ perl -i.orig process.pl sample.txt sample2.txt
-- before loop
ARGV: , ARGVOUT: , ^I: .orig
-- enter loop
ARGV: sample.txt, ARGVOUT: 2759153, ^I: .orig
ARGV: sample.txt, ARGVOUT: 2759153, ^I: .orig
ARGV: sample.txt, ARGVOUT: 2759153, ^I: .orig
ARGV: sample2.txt, ARGVOUT: 2759159, ^I: .orig
ARGV: sample2.txt, ARGVOUT: 2759159, ^I: .orig
ARGV: sample2.txt, ARGVOUT: 2759159, ^I: .orig
-- after loop
ARGV: sample2.txt, ARGVOUT: , ^I: .orig
上の結果から、以下の事実が読み取れる。
-
ARGVOUTは<>の読み込み中にのみセットされており、<>ループの前と後では存在しない。この時、printは通常通りSTDOUTに出る。 -
<>の読み込み中、ARGVOUTは$ARGVの値に応じて適切に変化する。これにより、複数ファイルを一度に処理できる。
先の例では、
$ perl -i.orig -e 'print "> $_" for <>' sample.txt
<>を限界まで読み込んでリストに保持してからループを回している。そのため、ループ中は既にARGVOUTは存在しない。
この場合、forをwhileにすればうまく動く。
$ perl -i.orig -e 'print "> $_" while <>' sample.txt