- ベストアンサー
Perlでのファイルの扱いでつまづいております。
- Perlでのファイルの扱いについて相談です。具体的には、ファイルAを読み取り、ファイルBに処理を施したものを書き込みたいです。また、ファイルBの書き込みと同時に読み取り、条件にマッチした文を書き換えたいです。しかし、ファイルの容量が大きいため、ループを避けたいです。どのような方法がありますか?
- Perlでファイルの扱い方についての質問です。具体的な要件は、ファイルAから内容を読み取り、それを加工したものをファイルBに書き込みたいというものです。また、書き込みと同時に読み取りを行い、ある条件にマッチした場合に文を書き換えたいです。ただし、ファイルの容量が大きいため、効率的な方法を知りたいです。
- Perlのファイル処理に関する問題です。ファイルAから読み取り、それを処理してファイルBに書き込みたいです。さらに、書き込みと同時にファイルBを読み取り、条件にマッチした文を書き換えたいです。ただし、ファイルの容量が大きいので、効率的な方法を教えてください。
- みんなの回答 (1)
- 専門家の回答
質問者が選んだベストアンサー
データの「書き換え」については、ファイルが言語・OS(ファイルシステム)を通じて実際にどのように扱われているのかをよく知っておくことが必要です。 Windows/UNIX系を問わず、一般的なファイルシステムのファイル操作では 「データのある部分を違う長さのデータで置き換える(部分挿入・部分削除)」 という処理は、ファイルの末尾でない限り一度の操作では出来ません。 原稿用紙に鉛筆で文章を隙間なく書くことを想像してみてください。 あとから途中に一文字付け足したり、一文字削除したくなったら、文章の終わりまで一度読んで記憶してから、その場所から最後までを上書きする必要があります。先頭や途中のマスを増やしたり減らしたりすることはできません。それと同じです。 Perlの場合、 ・tell()関数で現在の読み書き位置を調べる ・seek()関数で読み書き位置を指定する(ファイル先頭からのバイト数、またはファイル末尾) ・読み込み用関数(read()や行入力演算子<FH>) ・書き込み用関数(print()など) ・ファイル末尾の切捨て(truncate()) ・ファイル末尾の位置(=ファイルサイズ)を調べる(-s演算子) などを組み合わせてやれば、一応書き換えは実現できます。 ただしこの方法はデータが(ディスクの一時領域を節約できるというささやかなメリットはあるものの)処理は大変複雑になります。 唯一の例外は、書き換える元データと新データのサイズが一致している場合、いわゆる固定長データフォーマットであることが保証されている場合で、かつデータの部分削除機能が不要な場合は部分上書きのみで安全な書き換えができます。 で、解決策ですが、ディスク容量に十分な余裕(元データ容量の2倍+α)があるのであれば ・ファイルBから新規データを読む ・ファイルAから既存データを1行ずつ読む(読み込み専用として) ・書き込みは全て新規ファイルCに行う ・処理が最後まで終わったらファイルBを削除してファイルCをファイルBにリネームする(File::Copyモジュールを使えば、動作環境に依存しますがmove()だけで済ませられる場合も多くあります) としたほうが処理はシンプルになりますし、処理が何らかの理由で中断してしまった場合にファイルBの元データが破損する恐れもありません。 例としては以下のようになります。 # 「ファイルAの内容をファイルBに条件付きで反映させたい」のならば、 # 読み込みループを回すべきなのはファイルBだと思うのですが・・・ # 提示されたスクリプトが合っているという前提でとりあえず書きます。 open(my $NEWLOG, "<", "/△△/ファイルB") or die; flock($NEWLOG, 2); # とりあえずここではファイルBを排他処理用ロックファイルとして使う my $new = <$NEWLOG>; # ファイルB、先頭の1行しか読んでませんが本当にいいんでしょうか・・? open(my $LOGFILE, "<", "/○○/ファイルA") or die; open(my $TEMPLOG, ">", "/△△/ファイルC") or die; while(my $yomitori = <$LOGFILE>){ if(ある条件1){ print $TEMPLOG "$kakikomi\n"; } if(ある条件2){ my $kakikae = ''; # ←ここに書き換え用データ$kakikaeの生成処理が入ります $new =~s/\n/ $kakikae\n/; print $TEMPLOG "$new"; } } close($TEMPLOG); close($LOGFILE); unlink("/△△/ファイルB") or die; rename("/△△/ファイルC", "/△△/ファイルB") or die; close($NEWLOG); # ファイルBを閉じると同時に排他処理が解除される サンプルが20年前のPerl4の文法でしたので、Perl5の文法で書き直してあります。 # もし参考にした参考書やスクリプトがmyなどを一切使わないPerl4の文法で書かれていたら、まずそれらを捨てるところからはじめてください・・・。 最後に、同時に複数プロセスが同じデータファイルに対してアクセスする可能性がある場合(代表例はCGIスクリプト)、flock()などによる排他処理の実装をお忘れなく。でないとデータファイルが壊れます。 上記サンプルのように、読み込みと書き込みを同じ流れで行う場合、全体をひとつの排他処理でくるんでやる必要があります。 排他処理は書き込みを行うルーチンだけでなく、読み込み専用のルーチンにも入れておかないといけません(例えばこのルーチンがファイルBをunlink()した瞬間に他のプロセスがファイルBを読みに行った場合、エラーになったり既存データがなくなったように見えます)。 単体実行しかしないスクリプトであっても、うっかり複数のコンソールから同時実行してしまうミスに備えて、データファイルへのアクセス(またはプロセスそのもの)には常に排他制御を実装しておくことを強くお勧めします。
お礼
>my $new = <$NEWLOG>;# ファイルB、先頭の1行しか読んでませんが本当にいいん>でしょうか・・? 実際にはwhileを使い、順繰り読み込むようにしていました、惑わせてしまいすみません。 >排他処理は書き込みを行うルーチンだけでなく、読み込み専用のルーチンにも入れておか>ないといけません 注意していただくまでは、排他処理を考えておりませんでした。 ありがとうございます。 全体を通して、初心者にもわかりやすいよう丁寧に教えていただき、感謝いたします。 お陰で、現時点でファイルCに希望とするログに近いものを書き出すことができるようになりました。 あとはちょっとした修正と、処理速度を上げていくだけです。 本当にありがとうございました!