Perl複数ファイルから一行ずつ読み込んで比較

このQ&Aのポイント
  • Perlを使用して複数のファイルから一行ずつデータを読み込み、数値の大小を比較する方法について教えてください。初心者向けの場合でも理解できるようにお願いします。
  • ファイル構造は2行で1つのテキストデータを表し、奇数行にはテキストデータの情報が、偶数行にはIDが記載されています。テキスト内の数値を比較して、差が一定以上のテキストの情報を抽出したいです。
  • また、奇数行のテキスト情報はドット(.)の後に数値があり、その数値の大小を比較する際には括弧()で囲まれた数値の部分を使用します。
回答を見る
  • ベストアンサー

perl 複数ファイルから一行ずつ読み込んで比較

初心者向けのperl参考書がなんとか理解できる程度のperl初心者です。 一週間ほど思考錯誤してきたのですが、いくらスクリプトを書いてもどうしても自分では解決できない処理がありましたので、初めてなのですが、質問させていただきました。 二つの同じ形式の、一部情報だけ異なるファイルから一行ずつ配列を読み込んで、数値の大小を比較したいのですが・・・ ファイル構造は、下のように2行でひとつのtextデータの情報を表示する形式となってまして、 奇数行には、各textファイルの情報が、.以下の部分に4552221.2:(30.2):100、のように記載されています。 偶数行には、各テキストファイルの、IDとなる情報が、13333331のように記載されています。この奇数行と、偶数行一行ずつで、ひとつのtextデータの 情報を表しています。textというファイルは、text1から順に,text2000000まで、 2000万程度,行で言えば4000万行ほど存在しています。よって、file1もfile2も、同じ行であれば、同じtextで、同じIDのものを示しています。ずれはありません。 file1 ----------------------------- >text1. 4552221.2:(30.2):100 13333331 >text2 87999999 >text3. 3444444.1:(20.0):300 75533333 ----------------------------- 数値は意味が変わらない範囲で、比較しやすい数値に変えてあります。 この上のfile1と、下のfile2を比べたいのですが、 file2 ----------------------------- >text1. 4552221.2:(20.9):100 13333331 >text2 87999999 >text3. 3444444.1:(23.3):300 75533333 ----------------------------- 偶数行のtextファイルの欄が大事で、text名のドット(.)の後に、続けて数値が書いてある場合(上ではtext1とtext3が該当)、そのドットの後の括弧()で囲まれた中の数値の大小を比較して、 差が一定以上あるtextだけを出力するスクリプトが書きたいと思っているのですが、 これをいきなりすべてひとつのスクリプトにまとめるのは、私の知識と力量では到底無理なので、ひとつひとつ段階を踏んで処理していこうと考えました。 1、奇数行の、text.の後半に情報があるもの場合、tempファイルにその奇数行と、対になる偶数行を出力する。それを、file1、file2、個別に行う。(temp1、temp2を出力) file1で取り除かれる3、4行目のtext2は、file2でも必ず取り除かれるので、1の処理後も、file1と2の各行は、比較することが可能な状態です。 2、正規表現を使って、各行の()で囲まれた部分の最初の2桁の数字(一部は一桁の数字の場合もあり)、をtempに出力する。(temp3、temp4を出力) (この処理を行わなくても可能かもしれませんが、ややこしいので、数字だけ出すようにしました) 最後に、file1とfile2の各行の数字を一行ずつ読み込んで、数値に一定以上の差がある行の情報だけ出力したいのですが、各行を順番に分析できる方法は、while文か、配列に読み込む方法しか知りません。 前者のwhile文では、ファイルオープンは、ひたつ以上は同時には取り扱えないという情報を得たので、使えない思っています。後者の配列に読み込む方法は、桁が大きいので、実用的ではないと感じています。 二つ以上のファイルの各行の特定の数値データを、一行ずつ順に比較して、差があるものだけ抽出するにはどのようなスクリプトを書けばよいのか、ご教授下さい。 いろいろ調べましたが、(僕の調べ方が悪いと思いますが)適切なコマンドや方法にたどり着けません。 試してみた方法 ファイルハンドルを二つ指定して、while文の条件中に、andで条件を二つ指定してファイルハンドルから読み込んでやればよいと思ったのですが、実行するとエラーになってしまいます。ググっていろいろ調べてみたところ、ファイルハンドルの二つ指定はできないとの記述を見つけたので、whileの条件に複数のファイルハンドルを記述するこの方法はあきらめて、ここで、ストップしています。 まわりに、perlを扱える方がいらっしゃれば質問に伺うのですが。もしよろしければ、簡単でもご教授いただけますと幸いです。宜しくお願い申し上げます。

  • Perl
  • 回答数4
  • ありがとう数4

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

  • ベストアンサー
  • kumoz
  • ベストアンサー率64% (120/185)
回答No.2

元のファイルの file1 と file2 の対応関係にズレはないということなので、1つの while ループで処理できると思います。 #!/usr/bin/perl use strict; use warnings; open IN_1, "file1" or die "Can't open file1: $!"; open IN_2, "file2" or die "Can't open file2: $!"; open OUT, ">output.txt" or die "Can't open output.txt: $!"; while (my $f1_line = <IN_1>) { $f1_line .= <IN_1>; my $f2_line = <IN_2>; $f2_line .= <IN_2>; next unless $f1_line =~ /\([\d.]+\)/; my ($num_1) = $f1_line =~ /\(([\d.]+)\)/; my ($num_2) = $f2_line =~ /\(([\d.]+)\)/; if (abs($num_1 - $num_2) >= 5) { # 5 とブロック内は適宜変更 print OUT $f1_line, $f2_line; } } close IN_1; close IN_2; close OUT;

kazumaxneo
質問者

お礼

お返事が遅くなってしまい、申し訳ありません。こまかい部分で、いくつか良く分からない部分がありましたので、先頭数百行でとりあえずサンプルファイルを作って、それでご連絡いただいたスクリプトを使って処理してみました。 結果からいうとうまく出力できました。いまだなぜこのように書くのか?不明な点はありますが、このような短い記述で、差があったtextファイルだけ出力できたので、大変驚いています。本当にありがとうございます。特に参考になったのは、perlを使える皆様には当たり前の話かもしれませんが 1、whileの中に、ファイルハンドルを宣言するならば、ひとつのファイルハンドルで複数のファイルを処理できる。当たり前かもしれませんがこの発想に頭がまったく回りませんでした。 2、nextとunlessを組み合わせて、正規表現でマッチしなければ、whileの処理をスキップする部分。 next unless $f1_line =~ /\([\d.]+\)/; 3、マッチすれば、それを次の処理につなげるためそのまま新変数に代入する my ($num_1) = $f1_line =~ /\(([\d.]+)\)/; 4、新変数の差を条件にif文を走らせる。 本当に勉強になりました。ひとつの疑問でいろいろと学べ、まことに感謝しております。お忙しい中、このような素晴らしい記述をご教授いただき、まことにありがとうございました。 ところで、後学のためにお時間ありましたらもうひとつだけ教えていただきたいのですが、while文の中で、再度$f1_lineを定義しているのはどういう意味があるのでしょうか?IN2もそうですが。 while (my $f1_line = <IN_1>) { $f1_line .= <IN_1>; また、その際、=でなく.=としているのはどういう意味があるのでしょうか?自分なりに検索して調べてみましたが、.=というものの意味を示す記述は見つかりませんでした。これが分かれば、この記述法は、完全に理解できると思うのですが、ここだけよく分かりません。

その他の回答 (3)

  • TYWalker
  • ベストアンサー率42% (281/661)
回答No.4

二回目です。 大量のファイルを入力にして複雑な操作をするときのコツです。 (1)1本のプログラムで全部の処理をやろうとしない 細かい処理に分けて、1個1個を解こうとする。 具体的には中間ファイルを作る。 A=>Z という処理を A=>B、B=>C・・・と小分割し、1個1個を確実に検証する。 分からないところは、わからない部分だけ人の助けを求める。 (2) 少ない量のデータでテストして、完璧に走らせ、それが出来てはじめて、大量なデータに挑戦する。 これをまずやってから人に聞いた方がお互いのためです。

kazumaxneo
質問者

お礼

さらに追記連絡していただきありがとうございます。われながら、的を得ないへたくそな質問だと思いました。もう少し、的を絞ってコンパクトにまとめる更なる努力が必要だと感じました。 ただ、あえて解決済みの部分も含めて全体で質問させていただきましたのは、私の記述法では、いろいろ無駄がありすぎる気がしまして、文章処理に強いperlならば、もっと短く明朗快活に表現できるのではないか、と期待していた部分があります。とはいえ、貴重な時間を割いて回答してもらうのですからこのような質問姿勢ではダメですよね。 またどうしても解決できない問題がありましたら、これからもまた新しく質問投稿してしまうかもしれません。その際も、もしお時間ございましたら、またお相手していただければ幸いです。 1と2のコツは、たいへんタメになりました。大規模なデータですし、常にそういった配慮を忘れないようにしてがんばります。 ありがとうございました

  • shiren2
  • ベストアンサー率47% (139/295)
回答No.3

ファイルが2000000個あるわけですから、推定所用時間はちゃんと計算した方がいいですよ。 それほどの規模なら、データベースに放り込んでSQLで片付けるのが早いです。 現実的な時間で処理してくれると思います。

kazumaxneo
質問者

お礼

お返事ありがとうございます。excelのRなら、微々たるものですが、なんとか理解できるのですが、SQLというのはほぼ初耳でした。今後、こうした数千万行の処理を行う機会が増えそうなので、回答者様方に一歩でも近づけるように、日々努力を続けたいと思います。 参考というか、すごく役に立ちました!

  • TYWalker
  • ベストアンサー率42% (281/661)
回答No.1

全部読むのは大変なので読みませんでした。すみません。 open FILE, $file; while (<FILE>) {   $_の処理 } close FILE; という処理の意味は分かりますね。 これと、 open FILE, $file; @arr = <FILE>; close <FILE>; for (@arr) {   $_の処理 } という処理はまったく同じです。 下では@arrという配列にファイルの中身をドバーンと入れて処理しています。 これだと、 open FILE, $file_1; @arr1 = <FILE>; close <FILE>; open FILE, $file_2; @arr2 = <FILE>; close <FILE>; という風に複数のファイルを処理するのも簡単ですし、 for $i (0 .. $#arr) { # 0 から $#arr まで番号を$iに入れて処理する。$#arrは配列の最後の指標。   next if $i % 2 == 1; # $i を2で割った余りが1だったらループを飛ばす   $arr[$i] の処理 } と言う風にやれば奇数の行の処理も出来るでしょう。 大きな問題を小さな問題に分解する考え方はいいんですが、だったらその小さな問題のうち、うまくいかない部分だけを質問してください。 じゃないと質問に答える人はウンザリしてしまいます。 それから、エラーが出たというだけではなくて、実際にどんなプログラムを動かしたら、なんというエラーが出たかも教えてください。

kazumaxneo
質問者

お礼

貴重な時間を割いて早速お答ええいただき、ありがとうございます。たいへん迅速にお答えいただいたので驚いています。 配列に読み込むという方法、やってみます。まずは、100行程度だけ抽出したテストファイルを用いて試して見ます。 ただこの方法だと、考え方が間違っているかもしれませんが、行が1000万以上ある場合、一度メモリにすべてキャッシュされ、計算機に負担が大きいのではないかと懸念しています。ただ、配列に読み込む方法は、ぼんやりとはしってましたが、書くこともテストしたこともないので、大変勉強になりました。重ね重ねありがとうございます。今から試してみます。 >大きな問題を小さな問題に分解する考え方はいいんですが、だったらその小さな問題のうち、うまくいかない部分だけを質問してください。じゃないと質問に答える人はウンザリしてしまいます。 自分も投稿後に感じました。申しわけないと思っています。 >それから、エラーが出たというだけではなくて、実際にどんなプログラムを動かしたら、なんというエラーが出たかも教えてください。 質問欄にスクリプトを記載するには、文字制限でできませんでした。不親切すぎて回答に困りますよね。すいませんでした。 以下が、書いてみたスクリプト原文です。ここまででは、エラーは出てきませんでした。 #!/usr/bin/perl use strict; use warnings; #まずは、後半に情報があるtextファイルだけ抜き出す文を書く #入力、出力のファイルハンドルを指定 open (IN1, 'file1') or die "$!"; open (IN2, 'file2') or die "$!"; open (OUT1, '>temp1') or die "$!"; open (OUT2, '>temp2') or die "$!"; #if内で定数を定義したらエラーになるので、ここで定義。 my $flg1 = 0; my $flg2 = 0; #まずはfile1から while (my $data1 = <IN1>){ #()があれば、$flg1に2を代入  if (data1 =~ /\(.*\)/){  $flg1 = 2;  } #$flgが0以上なら出力し、flgの値を1減らす。真なら2行続けて出力される。  if ($flg1 > 0){  print OUT1 $data1;  $flg1 = $flg1 -1; } } #file2も同様に処理する while (my $data2 = <IN1>){ #()があれば、$flg2に2を代入  if (data2 =~ /\(.*\)/){  $flg2 = 2;  } #$flgが0以上なら出力し、flgの値を1減らす。真なら2行続けて出力される。  if ($flg2 > 0){  print OUT2 $data2;  $flg2 = $flg2 -1; } } close IN1; close IN2; close OUT1; close OUT2; #各行の()で囲まれた部分の最初の2桁の数字だけ抜き出す #入力、出力のファイルハンドルを指定 open (IN3, 'temp1') or die "$!"; open (IN4, 'temp2') or die "$!"; open (OUT3, '>temp3') or die "$!"; open (OUT4, '>temp4') or die "$!"; while (my $data3 = <IN3>){ #(数字2桁 )があれば真、数字2桁を変数に代入。 if ($seq3 =~ /(\()([1-9][1-9])/{ my $none1 = $1; my $compare1 = $2; #改行を挿入 my $compare2 = "$compare1\n"; print OUT3 $compare2; } while (my $data4 = <IN4>){ #(数字2桁 )があれば真、数字2桁を変数に代入。 if ($data4 =~ /(\()([1-9][1-9])/{ my $none2 = $1; my $compare3 = $2; #改行を挿入 my $compare4 = "$compare3\n"; print OUT4 $compare4; } close IN3; close IN4; close OUT3; close OUT4; #次に、各行の数値の大小を比較する。この先、2ファイルの同じ行を #比較する方法が分かりませんでした。 exit

kazumaxneo
質問者

補足

補足というか、文字制限で書けませんので追記しています。私の書いた、上のつたないスクリプトでも、 抽出の対象となる、下のような構造の二つのファイルから、 file1 >text1. 4552221.2:(30.2):100 13333331 >text2 87999999 >text3. 3444444.1:(20.0):300 75533333 ・ ・ ・ ・ file2 >text1. 4552221.2:(20.9):100 13333331 >text2 87999999 >text3. 3444444.1:(23.3):300 75533333 ・ ・ ・ ・ から、()内の左端の2桁の数値だけ抽出して、下のように、一行ずつ出力することまでは とりあえず、エラーなくできています。 file1(temp3として出力) 30 20 ・ ・ file2(temp4として出力) 20 23 ・ ・ 1000万以上の行のテキストの場合、どの程度の実用性があるのか不明ではありますし、もっとコンパクトに出来るとは思うのですが。 一番お聞きしたいのは、この2つのファイルの各行の数値を順に比較して、数値に一定以上の差がある場合、その数値を出力する、というのをどのような表現やコマンドを用いればできるのか?、です。 しつこくて大変恐縮なのですが、どなたかぜひ教えて下さい。お願いします。whileでのループや、forでの全配列でのループ処理では、file1かfile2のどちらか一方を順に処理することはできますが、どちらのファイルも、一行ずつ交互に読み込んで処理するにはどうすれば良いのでしょうか?このような考え方自体、根本的に間違っていたら申しわけありません。

関連するQ&A

  • 二つのファイルから一行ずつ取り出して計算

    aaa.txt と bbb.txtというファイルがあり、それぞれ 1.1 0.1 -0.2 0.9 … といった感じで一行ずつ数値が入っています。 シェルスクリプトを用いて、 この二つのファイルから一行ずつ取り出して足し算を行いたい (例えばaaa.txtの一行目が1.1、bbb.txtの一行目が0.9なら1.1+0.9=2.0) のですが、どうすれば良いでしょうか。

  • 二つのファイルから一行ずつ取り出して計算

    aaa.txt と bbb.txtというファイルがあり、それぞれ 1.1 0.1 -0.2 0.9 … といった感じで一行ずつ数値が入っています。 C言語を用いて、上記ファイルを読み込み、 この二つのファイルから一行ずつ取り出して足し算を行いたい (例えばaaa.txtの一行目が1.1、bbb.txtの一行目が0.9なら1.1+0.9=2.0) のですが、どうすれば良いでしょうか。

  • perlをコマンドラインで実行したい

    今はperlをコマンドプロンプト上でファイルをコンパイルして実行結果を別のテキストファイルに出力しています。(printなど) ただ、こまごまとした置換え・変換などいちいちテキストファイルにperlを書いてコンパイルして出力ファイルを開いて取り出すというやり方にわずらわしさを感じてきました。 簡単なスクリプトであれば、使い捨ての方がよく感じており、 コマンドプロンプトで試してみたのですが、長い文章やソースなどを変数にいれることができず、(改行がEnterにとられてしまう??)また、一行にずらずらとかかなくてはならないので、うまくいきません。 なにか、テキストエディタのような感じで、このようなperlスクリプトをコンパイルできるようなものはあるのでしょうか。 copalというものを使ってみたのですが、 ファイルハンドルなどが使えなかったのでだめでした。 どなたかご協力お願いします。

    • ベストアンサー
    • Perl
  • テキスト比較コマンドのdiffについて

    unixコマンドにdiffというのがありますね。2つのテキストファイルを比較してその違いを出力するというものです。このコマンドの意味がわからないところがあります。 テキストAとBの比較とは、 1 両者が全く同じかどうかを比較するということなのか、 2 テキストAとBに含まれている各行を取り出してどこかに一致する行があるかどうかをチェックするということなのか 3 あるいはそれ以外の意味 どのようなことを意味するのでしょうか。 1だったら、1行付け加わっただけでそれ以降が同じものであったとしても全部検索対象となり出力されます。 2だったら、何をしたことになるのでしょうか。全く同じ内容で行構成を入れ替えたら検索に引っかからないことになります。 私の希望としては1なのですが、そうするとわけがわかなないぐらい多くの出力結果となることが多いはずです。オプションなどで対応するとは思いますが、基本的には何をするコマンドなのでしょうか。 よろしくお願いします。

  • 【FLASH】テキストファイルを一行ずつ表示させたい

    まだまだFLASH初心者のものです。 不可能な質問かもしれませんが、もしご存知の方がいらっしゃいましたら教えてください。 まず、私が実行したいことをいかに記載いたします。 ------------------------------------ 入力フォームより50文字程度のお知らせを投稿 ↓ ログファイル(log.txt)に一行ずつ投稿されたデータが記録される(最大10記録されます。) ↓ ログファイルのデータを一行ずつフラッシュで表示 (横に流れるニュースのようなかんじで) ------------------------------------ ログファイルの各行の最初に news1=書き込んだデータ1 &news2=書き込んだデータ2 というように変数を入れてあげれば、フラッシュで同じ変数を設定したテキストフィールドに反映させることはできるのですが、フォームから投稿したデータの場合、 書き込んだデータ1 書き込んだデータ2 と変数を頭につけることができません。 (フォームのスクリプト(php)で変数をつけて出力する方法も考えてみましたが常に最新の記事をnews1、ひとつ前の記事をnews2に変更して出力する方法がわからなかったので・・) もし、変数なしで一行ずつを表示させる方法をご存知の方がいらっしゃいましたら教えていただけますでしょうか。 また、ほかに良い方法がございましたらそちらも教えて頂ければと思います。

    • ベストアンサー
    • Flash
  • ファイルの処理について

    数百行のテキストファイルを5行づつ取り込んで処理をしたいのですが。。 どうすればいいのでしょうか? 一行づつは可能なんですが。。。 While(<FILEHANDL>){ 処理 } ヘルプ~。。。。

    • ベストアンサー
    • Perl
  • ファイルを行ごとに比較するシェルスクリプトについて

    ファイルを行ごとに比較するシェルスクリプトをご教授ください。 例えば(master.txt)(a.txt)(b.txt)(c.txt)(ok.txt)(ng.txt)と4つのファイルがあり、 (master.txt)と(a.txt)の行を比較し(a.txt)の中のある行が(master.txt)の行と一致した場合(ok.txt)に (master.txt)の行と一致しなかった場合(ng.txt)に入れる。 その後(master.txt)と(b.txt)の比較し(a.txt)が使用したものと同じ(ok.txt)or(ng.txt)に入れる・・・ といったように繰り返していくシェルスクリプトはどのようにして作成するのでしょうか? txtファイルの中の行はランダムに入っており、x.txtの一行一行ををmaster.txtの全行と比較する必要があります。 自分で作ってみたものは、while文を2重で使い一行ずつ取り出しcase文で行が一致した物を(ok.txt)に入れる 所までは成功しているのですが、複数回繰り返す時にどのようなロジックで不一致行を(ng.txt)に入れる ことができるのかが考え付きませんでした。 よろしくお願いいたします。

  • C++で、テキストファイルを一行ずつ読み込んで配列に入れたいのですが、

    C++で、テキストファイルを一行ずつ読み込んで配列に入れたいのですが、うまくできません! テキストには -3.0        1.0        2.0 ・・・などのように一行に一つの数値が入って縦に並んでいます。 それをひとつずつ読み込み、新しい配列(たとえばa[])に順番にいれたいのです。 a[1]=-3.0,a[2]=1.0、・・・と なるように。 どうか、この初心者にご指導お願いいたします。

  • バッチでテキストを出力したときに改行ができない

    バッチファイルでテキストを出力するのですが、改行ができません echo 一行目 > C:\temp.txt ? echo 二行目 >> C:\temp.txt として結果C:\temp.txtファイル内に  一行目  二行目 ではなく  一行目  二行目 としたいのですが、、 ?にはいるものがわかりません。 echo " " >> C:\temp.txt としても""が入ってしまうし echo   >> C:\temp.txt (全角スペース)を入れても"ECHO は <ON> です。"とメッセージが出てしまいます。 わかる方お願い致します。

  • ファイルのパターンマッチ処理の書き方

    現在Perlを勉強中です。 あるところからhtmlファイルをダウンロードした後、リンクを抽出する というスクリプトを、以下のように書いてみました。 (ファイルの各行について、「href="」と「"」の間にある、「"」以外が連続する部分を抽出する。 という考えです。) ----ここから------------------------------------------------- #!/usr/bin/perl ##################################################### #ここに、あるところから temp.htm をとってくる処理がある。# ##################################################### # temp.htm 中、 href でリンクされているページを出力 open ( FILE, "temp.htm"); while (<FILE>){ print ("$1\n") if /.*href="([^"]*.html)".*/; } ---ここまで-------------------------------------------------- この抽出部分はもっと簡潔に書けるものでしょうか? と言いますのは、これでも、Cに比べれば簡単なのですが、sedなら、 sed -n 's/.*href="\([^"]*.html\)".*/\1/p' temp.htm の1行で済むところなので、 一々ファイルを開いてwhileでまわすという書き方をするのが面倒だな と思いましたので質問させて頂きました。 (もっとも、sedはそのために特化したコマンドなので さすがにそれと同等に簡単に書けることを望むのが間違いなのかもしれませんが、 「簡単なことは簡単に...」がモットーのようなので、ひょっとしてという期待がありまして。。。) これは、私の書き方が冗長なのか、それとも、perlではこう書くのが普通なのか、 perlの勉強を始めたばかりで判断がつきません。 どちらなのかご教授いただけると幸です。 また、他にもこう書けるまたは私ならこう書くという御意見もお伺いできたら嬉しいです。 宜しくお願いします。

    • ベストアンサー
    • Perl

専門家に質問してみよう