Perlでのファイルの扱いでつまづいております。

このQ&Aのポイント
  • Perlでのファイルの扱いについて相談です。具体的には、ファイルAを読み取り、ファイルBに処理を施したものを書き込みたいです。また、ファイルBの書き込みと同時に読み取り、条件にマッチした文を書き換えたいです。しかし、ファイルの容量が大きいため、ループを避けたいです。どのような方法がありますか?
  • Perlでファイルの扱い方についての質問です。具体的な要件は、ファイルAから内容を読み取り、それを加工したものをファイルBに書き込みたいというものです。また、書き込みと同時に読み取りを行い、ある条件にマッチした場合に文を書き換えたいです。ただし、ファイルの容量が大きいため、効率的な方法を知りたいです。
  • Perlのファイル処理に関する問題です。ファイルAから読み取り、それを処理してファイルBに書き込みたいです。さらに、書き込みと同時にファイルBを読み取り、条件にマッチした文を書き換えたいです。ただし、ファイルの容量が大きいので、効率的な方法を教えてください。
回答を見る
  • ベストアンサー

Perlでのファイルの扱いでつまづいております。

やりたいことは、"ファイルAを読み取り、その内容に処理を施したものをファイルBに書き込むという処理"です。 その上で、"ファイルBに書き込んだ内容を書き込みと同時に読み取り、 その上である条件にマッチした文を書き換える"といったことを実現したいです。 もともとのファイルの容量が非常に大きいので、何度もループを使うといったことはなるべく避けたいため、 ファイルBの書き込み・読み込み・書き換えを同時に行いたいのですが、 そもそもそういったことは可能なのでしょうか。 ※また、もともとのファイルの容量が非常に大きいので配列を使わず行う、 ということが前提条件としてあります。 現在のスクリプトの簡略化したものが以下となります。 open LOGFILE,"< /○○/ファイルA" || die("die"); open NEWLOG,"+< /△△/ファイルB" || die("die"); $new = <NEWLOG>; while($yomitori = <LOGFILE>){ if(ある条件1){ print NEWLOG "$kakikomi\n"; } if(ある条件2){ ファイルBの書き換えの処理 $new =~s/\n/ $kakikae\n/; print NEWLOG "$new"; } } close(NEWLOG); close(LOGFILE); 上記スクリプトで、ファイルAからファイルBへの書き込み、ファイルBの読み取りまではできておりますが、 ファイルBの書き換えは実現しておりません。 以上、お詳しい方がいらっしゃいましたら宜しくお願い致します。

  • Perl
  • 回答数1
  • ありがとう数2

質問者が選んだベストアンサー

  • ベストアンサー
  • N60-BASIC
  • ベストアンサー率80% (17/21)
回答No.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を読みに行った場合、エラーになったり既存データがなくなったように見えます)。 単体実行しかしないスクリプトであっても、うっかり複数のコンソールから同時実行してしまうミスに備えて、データファイルへのアクセス(またはプロセスそのもの)には常に排他制御を実装しておくことを強くお勧めします。

horiten
質問者

お礼

>my $new = <$NEWLOG>;# ファイルB、先頭の1行しか読んでませんが本当にいいん>でしょうか・・? 実際にはwhileを使い、順繰り読み込むようにしていました、惑わせてしまいすみません。 >排他処理は書き込みを行うルーチンだけでなく、読み込み専用のルーチンにも入れておか>ないといけません 注意していただくまでは、排他処理を考えておりませんでした。 ありがとうございます。 全体を通して、初心者にもわかりやすいよう丁寧に教えていただき、感謝いたします。 お陰で、現時点でファイルCに希望とするログに近いものを書き出すことができるようになりました。 あとはちょっとした修正と、処理速度を上げていくだけです。 本当にありがとうございました!

関連するQ&A

  • Perlで読み込んだファイル内の行数の検索について

    教えてください。 読み込んだファイル内にある空行・コメント行・処理行を一つのスクリプトで表示する書き方をおしえてください。 超初心者なので 今のところ、ファイルのオープンしかできていません。 #ファイルをオープンします open(F,"upload.cgi")|| die "upload.cgiをオープンできません:$!\n"; #ファイルを読みます while(<F>){ #内容を表示 print; } #ファイルを閉じます close(F); どこで、何の処理をしているかもコメントしていただけると助かります。 よろしくお願いします。

    • ベストアンサー
    • Perl
  • Perlのプログラミングについて

    Perlのプログラミングでつまづきました。 # ファイルから指定文字列を含む行を収集する # 入力ファイルのオープンと読み込み print( "入力ファイル名?" ); $n = <STDIN>; chomp( $n ); open( FIN, "<$n" ) or die "入力ファイルオープンエラー: $!\n"; $n = @a = <FIN>; close( FIN ); print( "$n 行読み込みました\n" ); # 行の収集 print( "検索文字列?" ); $x = <STDIN>; chomp( $x ); $ptn = $x; #指定の文字列 $x = @b = grep( /$ptn/, @a ); print( "$x 行見つかりました\n" ); # 出力ファイルのオープンと書き出し print( "出力ファイル名?" ); $y = <STDIN>; chomp( $y ); open( FOUT, ">$y" ) or die "出力ファイルオープンエラー: $!\n"; print FOUT ( $ptn, "\n" ); print FOUT ( $x, "\n" ); print FOUT ( @b ); close( FOUT ); というプログラムで実行すると C:\My Perl\pl>perl プログラムの実行.pl 入力ファイル名?sample1.txt 168 行読み込みました 検索文字列?k 45 行見つかりました 出力ファイル名?out3-24.txt 続行するには何かキーを押してください . . . となり出力ファイルの中身が表示されません。 どこを間違えているのかご指摘いただけないでしょうか?

  • Perl 教えてください。

    下記のような簡単なスクリプトなんですが、なぜかサーバーにアップすると動きません。 ローカルでコマンドプロンプトから直接実行するとまともに動作します。 パーミッションは「755」と「705」でやってみましたが。 perlのパスは合っていますし、実際同じ場所に置いた他のCGIは動作します。 #!/usr/bin/perl open (DT, "<./data/***/***.csv") or die "File '***.csv' Open Error."; @data = <DT>; $data[0] = ",,,,,,\n"; open (OUT,">./data/***/***1.csv") or die "File '***1.csv' Open Error."; print OUT @data; close (DT); close (OUT); 原因が分からず困っています。 解決策でなく、原因と思えるだけの回答で結構ですので何卒よろしくお願いいたします。

    • ベストアンサー
    • Perl
  • ファイルの先頭に追加書込みする方法について教えて下さい

    今まで、単にファイルの最後にデータを追加するように 下記のように書いていた部分を open(OUT,">>$logfile2") || &error; print OUT "カウント : \[ $pcount \]\n日  時 : \[ $time \]\n"; close(OUT); ファイルの先頭に追加していくようにしたいのですが、うまくいきません。 ミスティーネット・Perl・CGI講座というサイトを参考に下記のようにしてみました。 open(DATA,"+<$logfile2") || &error; seek(DATA,0,0); print DATA "\nカウント : \[ $pcount \]\n日  時 : \[ $time \]\n"; close(DATA); このように書くと追加書込みではなく、書き換えになってしまいます。 また、+<の部分を>>にすると変更前と同様、ファイルの最後に追加されてしまいます。 どこがどういけないのか判る方がみえましたら教えて下さい。 よろしくお願いいたします。

    • ベストアンサー
    • CGI
  • perlについての質問です

    &あああ("ファイル名1", "AGT"); &あああ("ファイル名2", "ACA"); &あああ("ファイル名3", "TAT"); &あああ("ファイル名4", "TGA"); sub あああ{ open(FILE, "処理させるファイル") or die "$!"; my ($file, $tag) = @_; open(NEWFILE, "> $file") or die "$!"; my $x = 0; my @ti; while(<FILE>){ if($_ =~ "^>"){ $x ++; $ti[$x] = $_; } elsif($_ =~ "^$tag"){ print NEWFILE $ti[$x].$_; } } print "$x\n"; close NEWFILE; } このプログラムをサブルーチンではなく forかなんかで実行させ 自由にファイル数と$tagを設定し 実行できるようにしたいのですが できますでしょうか>< forをまわす回数を指定しSTDINなんかで指定し そのforの中でファイル処理に必要な条件や 吐き出すファイルの名前をSTDINで指定するようなものを書きたいです ちなみにperl初心者です 質問が意味不明かもしれないです>< よろしくおねがいします

    • ベストアンサー
    • Perl
  • 行を指定して削除する方法PERL

    ある文字を検索して、その行を含む&1行前と2行後ろの行までを削除するスクリプトを書きたいのですが、上手くいきません。 検索して行番号を獲得して、 $rowという変数に入れました。 それをさらに $a :1行前 $b :2行後ろの行番号に格納しました。 問題は削除するところが上手く行きません。 next if で $aから$bの行番号を削除して、と頼んでいるのですが、空のファイルに上書きされてしまいます。 お願いです。この方法で何が間違っているかを教えてください! #!/opt/perl/5.8.0/bin/perl -w print "Content-type: text/html\n\n"; use CGI qw(:standard); use CGI::Carp qw/fatalsToBrowser/; $filename = "../XML/link.xml"; $new = "../XML/link.xsl"; open(FILE, $filename) or die "Can't open `$filename': $!"; while (<FILE>) { if($_ =~ /HRWeb/){ #print "$."; $row = $.; $a = $row-1; $b = $row+2; print "HRWeb delete rows $a through "; } } &delete ($a, $b); sub delete{ open( OLD, "< $filename" ); open( NEW, "> $new" ); while ( <OLD> ) { next if /$a/../$b/; # copy everything but $a through $b print NEW $_; }print "$a deleted $b"; close( OLD ); close( NEW ); rename( $filename, "$filename.orig" ); rename($new, $filename ); }

    • ベストアンサー
    • Perl
  • perlについて、教えてください。

    perlについて、教えてください。 「apple.html」と「bigin.html」に、それぞれ, textboxが一つあり、入力値をkeep.cgiで受取り、 「keep.txt」に書込もうと考えています。 a.htmlのテキストボックスに、入力して、submitボタンをクリック しても、「keep.txt」に何も書き込まれません。 「keep.txt」に、 _______ a=100 b=200 ________ のように、書込みたいと考えています。 下記、keep.cgiのどこが間違っているのか、教えてください。 ___以下、「keep.cgi」です。_______________________________ #! c:/perl/bin/perl if($ENV{'REQUEST_METHOD'} eq 'POST') { read(STDIN, $query, $ENV{'CONTENT_LENGTH'}); } else { $query = $ENV{'QUERY_STRING'}; } #$queryに「●_text=●」が入っている。 #ファイルを開く、読み書き用。 $open_file_name = "+<keep.txt"; open(A,$open_file_name); #開いたファイルを、1行ずつ読み込む。 while(<A>){ #$_には、keep.txtの、1行目が入っている。 #$_(読込んだ行)に「●_text」があるか、チェックする if($_ =~ /$query/){ #あった場合 next; } #読込んだ行に、$query以外なら、配列に。 @keep = $_; @keep = $query } #ファイルを閉じる。 close(A); #ファイルを削除する unlink($open_file_name); #削除したのと、同じファイル名で新規作成 $new_file = ">>keep.txt"; open(A,$new_file); #再度、作成したファイルに、配列@keepを書込む。 print A @keep; #ファイルを閉じる close(A); print "Cotent-type:text/html\pirnt n\n"; print <<EOF; <body bgcolor="pink"> <font size="4"> 入力値をファイルに書込みました。<br> </font> EOF exit; ______________________ 宜しくお願いします。

    • ベストアンサー
    • CGI
  • ログファイルの記録数

    # ログ更新 unshift(@new,"$num<>$addr\n"); open(OUT,">$logfile") || &error("Write Error: $logfile"); print OUT @new; close(OUT); 上記ログの書込みに、最大保存数を設定したいのですが、どのようにすればいいのでしょうか。 $max = 3; とした場合、3行超えたら古いファイルを消したいと考えています。よろしくお願いいたします。

    • ベストアンサー
    • Perl
  • lockについて

    $file = 'file.log'; ------------------------------------ sub a{ &lock;ロック 1: open(IN, $file); # ファイルを開く 2: $count = <IN>; # カウンターを読み出す 3: close(IN); # ファイルを閉じる 4: $count++; # カウンターをひとつ増やす 5: open(OUT, "> $file"); # ファイルを開く 6: print OUT "$count\n"; # ファイルにカウンタを書き込む 7: close(OUT); # ファイルを閉じる 8: print "$count\n"; &unlock;ロック解除 } ------------------------------------ sub b{ 1: open(IN, $file); # ファイルを開く 2: $count = <IN>; # カウンターを読み出す 3: close(IN); # ファイルを閉じる } ------------------------------------ サブルーチンaではロック処理を行っていますが、 サブルーチンbでは書き込む処理がないためロック処理を行っていません。 読み込むファイルは同じでなのですが、 サブルーチンbにもロック処理を入れた方がいいのでしょうか?

    • ベストアンサー
    • Perl
  • perl初心者です。宜しくお願い致します。

    ファイルの容量が大きく。perlを使用してデータの集計をしています。 "A"がきたらflag1をたてなさい。 "B"がきたらflag2をたてなさい。 "C"がきたらflag3をたてなさい。 これでAとBとCを抜き取ること&AからCまでの時間を取得したのですが、 Bの数のmaxの値だけを抜き取りたいのですが、Bがきたときの数をすべて 出力してしまいます。下記の文だと、Bが4回きたら、1,2,3,4と出力してしまいます。 それで4だけを出力したいのですがどのように書き換えたらようか教えて頂けますでしょうか。 '----------------------------------------------------------------------------- open (IN,"< $ARGV[0].txt") or die; open (OUT,"> $ARGV[0]_out.txt") or die; $flag =0; my $a, $b, $c; $count = 0; ####################################################### while($line =<IN>){ ($time,$data) = split(/\s+/,$line); if($data eq "A") { $flag=1; $a = $time; #print OUT $line; #print OUT "\n"; } elsif($data eq "B"){ $flag=2; $count++; $count == $data; print OUT ("$count\n") } #print OUT ("$count\n"); elsif($data eq "C"){ $flag=0; $count=0; $b = $time; $c = $b - $a; print OUT ("time $c\n") } } -------------------------------------------------------------------------------

専門家に質問してみよう