Inplace-Edit #2

下のエントリの続き。

元記事で条件を端折ってしまったのですが、ファイル一覧はスクリプト内で生成されるので、INPLACE_EDIT(や、ワンライナー)は使用できないように思います。
File::Inplaceは初めて知りました。これで良さそうですが、できればコアモジュールで何とかしたいと思っています。

条件

  • ファイル一覧は、抽出条件が複雑なためスクリプト内で生成されます(コマンドラインから与えられません)。
  • できればコアモジュールだけで・・・

※1番目の条件は、ファイル一覧抽出後にそれらを引数にしてexecするという手もありますね。と書いてみたものの、引数が多くなるとダメになってしまうので、この案は却下。

えーと、exec しちゃっていいなら xargs を組み合わせるのが定番かと思います(UNIX系OSの場合)。filelist.pl をファイル一覧を改行区切りで出力するスクリプト、replace.pl を inplace-edit で置換するスクリプトとして、以下の様な感じ。

% ./filelist.pl | xargs ./replace.pl

パスに空白を含む可能性を考慮するなら、./filelist.pl は "\000" を区切り文字にするようにして、以下のように。

% ./filelist.pl | xargs -0 -e ./replace.pl

あと、邪道な気もするけど、@ARGV を書き換えちゃうという手も。
たとえば、置換対象が「あるディレクトリ以下にあるテキストファイル全て」てな感じの場合こうする。

use strict;
use warnings;
use File::Spec;

sub filelist {
    my ($path, $list) = @_;
    $list ||= [];

    opendir my $dir, $path or die;
    while (my $file = readdir $dir) {
        next if $file =~ /\A [.]+ \Z/x;
        my $file_path = File::Spec->catfile($path, $file);
        if ( -d $file_path ) {
            filelist($file_path, $list);
        }
        elsif ( -T $file_path ) {
            push @$list, $file_path;
        }
    }

    return $list;
}

sub main {
    my $path = $ARGV[0] || '.';
    my $filelist = filelist($path);

    local $^I = "";
    local @ARGV = @$filelist;
    while (<>) {
        s/foo/bar/;
        print;
    }
}

main;

別にファイルリストを一度に配列に入れる必要も無くて、置換部分は

while (my $file = next_file) {
    local @ARGV = ($file);
    while (<>) {
       s/foo/bar/;
       print;
    }
}

な感じでも大丈夫。