Perlでファイルの中の特定の行を置換するには

[上に] [前に] [次に]
バイト警備員 [E-Mail] 1999/09/16(木) 11:34:38
Perlを使ったCGIでアンケートを作成しているのですが、
サーバ上のファイルの文字列の置換で躓いています。
例えば以下のような内容のファイルがあって、

the quick brown FOX
jumps over the lazy dog.

これの一行目「FOX」を「CAT」して保存するような方法はないでしょうか。
以下のようなコードを書きましたが・・・

open(FILE, "+<xxx.txt");
while(<FILE>) {
    if ($_ =~ /FOX/) {
        s/FOX/CAT/;
        print;
    }
}

これでは、

the quick brown FOX
the quick brown CAT

・・・となり、二重に書き込まれるばかりか次の行が失われてしまいました。
全て読み込んで置換したあと別のファイルに書き込むというのは、できれば避けたいのですが。

よろしくお願いします。

バイト警備員 [E-Mail] 1999/09/16(木) 11:45:09
すみません、コードに誤りがありました(^^;

s/FOX/CAT;
print;

の部分は、

print FILE $_;

が正しいです。(^^;

B-Cus 1999/09/16(木) 14:43:37
無理です。

open(IN,"xxx.txt");
@buf=<IN>;
close(IN);
置換処理
open(OUT,">xxx.txt");
print OUT @buf;
close(OUT);

とするか、一度に@bufに全部格納するのが嫌なら

open(IN,"xxx.txt");
open(OUT,">xxx.new");
while (<IN>){
 s/FOX/CAT/;
 print OUT;
}
close(IN);
close(OUT);
unlink("xxx.txt");
rename("xxx.new","xxx,txt");

としましょう。

# …と僕は思うんだけど、もしかしてできるのかなぁ?

ふじ 1999/09/16(木) 16:28:08
># …と僕は思うんだけど、もしかしてできるのかなぁ?
FOX と CAT のように、文字数が同じなら出来なくもないですが・・・

open(FILE, "+<xxx.txt");
で開いて、FOXが出て来る位置(先頭からのバイト数)
を見つけて、そこに seek して print FILE "CAT"; とすれば。

まあでも文字数が違ったりするととっても面倒になるだろうし、
私も B-Cus さんが示された方法をお勧めします。

アトム 1999/09/16(木) 19:42:53
やってることは、B-Cus さんと同じですが、
もう少し簡潔に書く方法として、

@ARGV = ("data.txt");
$^I = "bak";
while (<>) {
    s/FOX/CAT/;
    print;
}
unlink ("data.txtbak");

(↑あんまり変わらないか(^_^;)

>全て読み込んで置換したあと別のファイルに書き込むというのは、
>できれば避けたいのですが。
置き換えは1回だけでいいの?
全て読み込まないと、「変換漏れ」が生じるけど。

特定の行の位置が何行目かがわかっていて、
変換前文字数と変換後の文字数が同じ(例えば、/FOX/CAT/は可だけど、
/FOX/CA/は不可)なら、

例えば3行目だということが分かっている時

open(FILE, "+<data.txt");
$gyou = 3;

for ($i = 1; $i < $gyou; $i++) {
    $bun = <FILE>;
    $iti = tell(FILE);
}
$bun = <FILE>;

if ($bun =~ /FOX/) {
    $bun =~ s/FOX/CAT/;
    seek (FILE, $iti,0);
    print FILE $bun;
}
close (FILE);


もし、特定の行が1行目なら、もう少し簡単に、
(この場合も(変換前の文字数)=(変換後の文字数)でないとダメ)

open(FILE, "+<data.txt");

$bun = <FILE>;

if ($bun =~ /FOX/) {
    $bun =~ s/FOX/CAT/;
    seek (FILE, 0,0);
    print FILE $bun;
}
close (FILE);

長月 1999/09/17(金) 08:49:13
perl -pi.bak -e "s/FOX/CAT/" filename

スクリプトなら、strcng.cgiを
#!/usr/local/bin/perl -pi.bak

s/FOX/CAT/;
としたとき、
strcng.cgi?filename

filename.bakといいうバックファイルも生成するけど

バイト警備員 [E-Mail] 1999/09/17(金) 14:07:46
[[解決]]
みなさん、ありがとうございます。サンプルコードまでつけていただいて・・・。

結論としては「できないことはないが、バッファリングして別ファイルに書き込む方が無難」ということでしょうか。
実はファイルに書き込んだデータが膨大になった場合、
一度全て読み込んで別ファイルに書き出すのでは、
メモリを食うしディスクアクセスが多くなり遅くなるのではと心配したのです。

ふじさんアトムさんのseekを使う方法を工夫(動的に行長を取得するなど)してもよさそうですが、
今回はB-cusさんの一度にバッファに読み込まない方法でいきたいと思います。
長月さんのコマンドラインからのPerlの使い方は、
手動でデータの変換が必要になったときに参考にさせていただきます。

がんばってスクリプトを完成させたいと思います。
ありがとうございました。
また何かあればよろしくお願いします。

[上に] [前に] [次に]