• ベストアンサー

指定の行数目から行を抽出する

いつもお世話になっております. 環境はWindows XP Pro でActiveperlを使っています. Perlでしたいことは,「指定の行数目から行を抽出する」ことです. 具体的には以下のようにしたいと思っております. data.txt A B C D E F line.txt 2 4 6 output.txt B D F 先ほどある方からサンプルソースを教えてもらったのでそれをベースに作ってみましたが,出力のoutput.txtが空のままです. use strict; use warnings; use feature ':5.10'; use IO::File; open my $file2, '<', 'line.txt' or die "can't open input $!"; chomp(my @subjects = <$file2>); close $file2; open my $newfile, '>>', 'data_out.txt' or die "can't open output $!"; open my $file, '<', 'data.txt' or die "can't open input $!"; while (my $line = <$file>) { chomp $line; foreach my $line (@line) { print $line; if ($. eq $subjects){ say {$newfile} $line; } } } close $file; close $newfile; どこが間違っているのでしょうか.ご指摘ください.よろしくお願いします.

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

  • ベストアンサー
  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.8

例えば: まずサブルーチンを用意します: sub printSpecifiedLines { my ($infile, $outfile, @subjects) = @_; open(my $infh, '<', $infile) or return; open(my $outfh, '>>', $outfile) or die "出力できない\n"; while (my $line = <$infh>) { foreach my $subjects (@subjects) { if ($. == $subjects){ 処理 say {$outfh} $line; } } } } 入力ファイルがオープンできないときはだまって return するようにしてみました. で, これを使う方では open my $file, '<', 'line.txt' or die "can't open input $!"; chomp(my @subjects = <$file>); close $file; @dd_max = ( 0, 31,29,31,30,31,30,31,31,30,31,30,31 ); for ($mm=1;$mm<13;$mm++){ for($dd=1;$dd<$dd_max[$mm-1];$dd++){ for($hh=0;$hh<24;$hh++){ $filename = sprintf("2000-%2.2d-%2.2d_%2.2d.txt",$mm,$dd,$hh); printSpecifiedLines($filename, "./out/$filename", @subjects); } } } で呼び出す, と. とりあえず, これでファイルのオープン/クローズの整合性はとれるはずです. ここでは @subjects が全てのファイルで共通なのでメインの方で読み込んでますが, もちろんサブルーチンの方で読み込むという考え方もあります.

oswll
質問者

お礼

Tacosan様 ありがとうございます.毎度のことながらお礼をいくら言ってよいのかわからないほどです.実際にお会いして,感謝を伝えたいくらいです. 今後もお世話になるかもしれません.そのときもどうかよろしくお願いします. ところで,Tacosan様がこれほどここで質問に答える理由はなんですか? もし,よろしければお聞かせください.だめな理由があればいいですからね.

その他の回答 (8)

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.9

いや, とりあえず「どの質問に答えるか」ということについては「自分が答えられそうか」ということをまず考えて, その上でわりときまぐれですね. ただ, 自分が答えておいて「わからん」と言われるとやっぱり気になるので, 一度答えた質問に対してはなるべく解決してもらえるとうれしいなぁ, と思うわけです. あ, そうそう, サブルーチンじゃない方の for は多分 .. を使って for my $mm (1 .. 12) とか書いた方が Perl 的だと思います. .... すみません, いくつかバグってますね. @dd_max の最初の 0 は消してください & $dd に対する範囲が間違ってます.

oswll
質問者

お礼

なるほど納得しました.そうですね,あのままだと2月から始まってしまいました.現在データの処理中です.2-3日はかかりますね. ありがとうございました.回答を締め切ります.

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.7

気になったこと: ・$dd に対するループはおかしいですね. このままだと無限ループ. ・$file に対する close が 2回入ってますな. ・ファイルハンドル IN に対する open/close のタイミングがなんかおかしい. そのエラーそのものがどこで出てるかはちょっとわかりませんが, 当該ファイルが存在するかどうかは確認した方がいいのでは? 実際の処理の部分はサブルーチンにした方がわかりやすいような気がしますが, そこはまあ趣味なので.

oswll
質問者

お礼

Tacosan様 いつもご回答ありがとうございます. サブルーチンで作りたかったのですが,ファイルを読み込んで,サブルーチンにもっていくまでが分からなくてこのような形になりました. それでもだめでしたね. 雰囲気だけでも良いので全体的な流れを書いてもらえないでしょうか. よろしくお願いします.

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.6

OK. あと問題になるのは, ここで出てくる「2個の入力ファイル」の関係. つまり, 入力ファイルとして data.txt と line.txt がある. data.txt の方は可変だからさておいて, line.txt はどうなんでしょうか? これも, 状況によって ・全てのデータファイルで共通 ・各データファイルごとにすべて別々 ・(データファイルがサブディレクトリに入っている場合には) 各サブディレクトリごとで共通 など, いろんな場合が考えられます. と一応書いておくけど, 現状からいちばん簡単に対処するなら, まず「1つのファイルを処理して 1つのファイルに出力する」ところ (つまり「今できている」ところ) を「3個のファイル名を引数に持つ」サブルーチンにします. で, 各入力ファイルに対し以下のループを回せばいいはず: ・入力ファイルの名前から出力ファイルの名前を作る (必要なら行番号の書かれたファイルの名前も作る). ・先に作ったサブルーチンを適切な引数で呼び出す. 入力ファイルの名前をどのように取り出すかについては, ファイルシステムにおいてどのように配置されているかに依存します.

oswll
質問者

お礼

ここ2日間粘ってみましたがどうもうまくいきません.ソースが以下です. 入力ファイル(2000-01-01_00.txtから2000-12-31-23.txtまで)をfor文で作成し, use warnings; use IO::File; @dd_max = ( 31,29,31,30,31,30,31,31,30,31,30,31 ); for ($mm=1;$mm<13;$mm++){ for($dd=1;$dd<$dd_max[$mm-1];$dd+1){ for($hh=0;$hh<24;$hh++){ $filename = sprintf("2000-%2.2d-%2.2d_%2.2d.txt",$mm,$dd,$hh); open(IN,$filename); open my $file, '<', 'line.txt' or die "can't open input $!"; chomp(my @subjects = <$file>); close $file; open my $newfile, '>>', "./out/$filename" or die "can't open output $!"; while (my $line = <IN>) { chomp($line); foreach my $subjects (@subjects) { if ($. == $subjects){ 処理 say {$newfile} $line; } } } close $file; close $newfile; } } } close(IN); エラーとして, readline() on closed filehandle IN at line.pl line 19 がでます. ちなみに19行目は while (my $line = <IN>) { です. いろいろ試してみましたが成功しません.修正点をどなたかご指摘ください.

oswll
質問者

補足

Tacosan様 いつもお世話になっております. 入力ファイルのline.txtは「全てのデータファイルで共通」です. なるほど, 1つのファイルを処理して 1つのファイルに出力する」ところ (つまり「今できている」ところ) を「3個のファイル名を引数に持つ」サブルーチンにします. で, 各入力ファイルに対し以下のループを回せばいいはず: ・入力ファイルの名前から出力ファイルの名前を作る (必要なら行番号の書かれたファイルの名前も作る). ・先に作ったサブルーチンを適切な引数で呼び出す. 入力ファイルの名前をどのように取り出すかについては, ファイルシステムにおいてどのように配置されているかに依存します. 以上の件了解しました. まずは自分でやってみます....できなければ申し訳ありませんがお助けいただくかもしれません. なお,入力するファイルは2000-01-01_00.txtから2000-12-31-23.txtまであります.

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.5

ディレクトリ内の各テキストファイルに対して処理するのはいいんですけど, 出力はどうしますか? ・各ファイルごとに (それなりなファイル名の) ファイルに出力する ・全てのファイルに対する出力を全部まとめて 1つにする どっちにします?

oswll
質問者

補足

Tacosan様 ご連絡ありがとうございます. ・各ファイルごとに (それなりなファイル名の) ファイルに出力する にしたいと思っております. 毎度ながらTacosan様にはご返答いただき,感謝致しております.

  • sakusaker7
  • ベストアンサー率62% (800/1280)
回答No.4

別に名前を出せという意味でもなくて、単に「別の質問で教えてもらった」 でいいんじゃなかろうかということです。 んで、line.txtに抜き出したい行の行番号があるということであれば #!/usr/bin/perl use strict; use warnings; use feature ':5.10'; open my $line_file, '<', 'line.txt' or die $!; my @lines = map { chomp; $_-1} <$line_file>; close $line_file; open my $data_file, '<', 'data.txt' or die $!; my @data = (<$data_file>)[@lines]; close $data_file; say @data; とか。 考え方を示すためだけのものなので、いろいろと手を抜いています。 最初の質問で内容の丸呑みしようとしてたのだから、ここで例に出しても 問題ないと考えました。

oswll
質問者

お礼

sakusaker7様 ご回答ありがとうございます. なるほど,mapを使うとさらに簡潔にできるんですね.勉強になります. そこで,本題であるディレクトリ内のテキストファイルへの処理なのですが,私はずっと以下の方法で処理してきたため,今回の方法をどう適用したらよいのか分かりません.教えていただけないでしょうか. my $dirname = '.'; opendir(DIR, $dirname) or die "$dirname: $!"; while (my $dir = readdir(DIR)) { next unless (-f $dir); next unless ($dir =~ /\.txt$/); open(FILE, $dir) or die "$dir: $!"; my @file = <FILE>; close(FILE); foreach my $line (@file) { 処理 open(NEWFILE, "> $dir") or die "$dir: $!"; print NEWFILE @file; close(NEWFILE); }

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.3

foreach my $subjects (@subjects) { if ($. eq $subjects){ say {$newfile} $line; } } の部分は全部まとめて say {$newfile} $line if grep { $. == $_ } @subjects; にできると思うし, さらに say を使ってるからには 5.10 だと思うので say {$newfile} $line if $. ~~ @subjects; までできないかな? あ, 今は数値として比較してるので, if の条件は eq より == の方が適切ではないでしょうか. でも, 「リダイレクトが使えない」ってどういうことなんだろう?

oswll
質問者

お礼

なるほど,短くできるんですね.処理速度は向上しますか. そうですね,==の方が適切ですね.以前"=="で失敗したのでeqにずっとしていました. リダイレクション機能は1対1だと,perl syori.pl > output.txt などのようにできることは知っているのですが,これは大量の処理を目的に作ろうとおもっているので考えていませんでした.

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.2

「指定の行数目から行を抽出する」って, 意味わかんないよね. でも, 単に 1つのファイルに出力するだけならプログラム内で出力ファイルを書くよりも外でリダイレクトさせた方が柔軟だろうとか, 5.10 ならスマートマッチ使えばいいのにとかは思う.

oswll
質問者

お礼

意味が分かりづらくてすみません.Tacosan様には以前もお世話になりました.「指定の行数目から行を抽出する」とはline.txtにある数字(2や4)を利用して,data.txtの行(line.txtにある数字)を抽出するという意味です.実は,16000ほどファイルがあるので,リダイレクション機能は使えないのです.

  • sakusaker7
  • ベストアンサー率62% (800/1280)
回答No.1

警告メッセージでてませんか? while (my $line = <$file>) { chomp $line; foreach my $line (@line) { print $line; if ($. eq $subjects){ ・内と外のループで同じ名前の変数($line)を使っている。 ・$subjectsという変数を宣言していない しかし「ある方」はねーだろ

oswll
質問者

お礼

sakusaker7様 名前を出すと良くないかと思いましたのですみませんでした. とりあえず自分でできました.ありがとうございました. use strict; use warnings; use IO::File; open my $file2, '<', 'line.txt' or die "can't open input $!"; chomp(my @subjects = <$file2>); close $file2; open my $newfile, '>>', 'data_out.txt' or die "can't open output $!"; open my $file, '<', 'data.txt' or die "can't open input $!"; while (my $line = <$file>) { chomp($line); foreach my $subjects (@subjects) { if ($. eq $subjects){ say {$newfile} $line; } } } close $file; close $newfile;

関連するQ&A

専門家に質問してみよう