みんなの「教えて(疑問・質問)」にみんなで「答える」Q&Aコミュニティ

こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

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

いつもお世話になっております.

環境は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;

どこが間違っているのでしょうか.ご指摘ください.よろしくお願いします.

投稿日時 - 2008-11-16 20:41:12

QNo.4483943

すぐに回答ほしいです

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

例えば:
まずサブルーチンを用意します:
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 が全てのファイルで共通なのでメインの方で読み込んでますが, もちろんサブルーチンの方で読み込むという考え方もあります.

投稿日時 - 2008-11-19 22:03:54

お礼

Tacosan様

ありがとうございます.毎度のことながらお礼をいくら言ってよいのかわからないほどです.実際にお会いして,感謝を伝えたいくらいです.
今後もお世話になるかもしれません.そのときもどうかよろしくお願いします.

ところで,Tacosan様がこれほどここで質問に答える理由はなんですか?
もし,よろしければお聞かせください.だめな理由があればいいですからね.

投稿日時 - 2008-11-19 22:17:51

ANo.8

このQ&Aは役に立ちましたか?

4人が「このQ&Aが役に立った」と投票しています

回答(9)

ANo.9

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

投稿日時 - 2008-11-20 02:25:34

お礼

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

投稿日時 - 2008-11-20 02:42:16

ANo.7

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

投稿日時 - 2008-11-19 20:17:18

お礼

Tacosan様

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

投稿日時 - 2008-11-19 20:47:26

ANo.6

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

投稿日時 - 2008-11-17 11:40:38

補足

Tacosan様

いつもお世話になっております.
入力ファイルのline.txtは「全てのデータファイルで共通」です.
なるほど,

1つのファイルを処理して 1つのファイルに出力する」ところ (つまり「今できている」ところ) を「3個のファイル名を引数に持つ」サブルーチンにします.
で, 各入力ファイルに対し以下のループを回せばいいはず:
・入力ファイルの名前から出力ファイルの名前を作る (必要なら行番号の書かれたファイルの名前も作る).
・先に作ったサブルーチンを適切な引数で呼び出す.
入力ファイルの名前をどのように取り出すかについては, ファイルシステムにおいてどのように配置されているかに依存します.

以上の件了解しました.

まずは自分でやってみます....できなければ申し訳ありませんがお助けいただくかもしれません.

なお,入力するファイルは2000-01-01_00.txtから2000-12-31-23.txtまであります.

投稿日時 - 2008-11-17 12:36:29

お礼

ここ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>) { です.
いろいろ試してみましたが成功しません.修正点をどなたかご指摘ください.

投稿日時 - 2008-11-19 17:27:56

ANo.5

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

投稿日時 - 2008-11-17 01:21:14

補足

Tacosan様

ご連絡ありがとうございます.
・各ファイルごとに (それなりなファイル名の) ファイルに出力する
にしたいと思っております.

毎度ながらTacosan様にはご返答いただき,感謝致しております.

投稿日時 - 2008-11-17 07:26:52

ANo.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;

とか。
考え方を示すためだけのものなので、いろいろと手を抜いています。
最初の質問で内容の丸呑みしようとしてたのだから、ここで例に出しても
問題ないと考えました。

投稿日時 - 2008-11-17 00:29:16

お礼

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);
}

投稿日時 - 2008-11-17 00:56:38

ANo.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 より == の方が適切ではないでしょうか.
でも, 「リダイレクトが使えない」ってどういうことなんだろう?

投稿日時 - 2008-11-16 23:22:31

お礼

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

投稿日時 - 2008-11-16 23:44:45

ANo.2

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

投稿日時 - 2008-11-16 21:50:30

お礼

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

投稿日時 - 2008-11-16 22:04:39

ANo.1

警告メッセージでてませんか?

while (my $line = <$file>) {
chomp $line;
foreach my $line (@line) {
print $line;
if ($. eq $subjects){

・内と外のループで同じ名前の変数($line)を使っている。
・$subjectsという変数を宣言していない

しかし「ある方」はねーだろ

投稿日時 - 2008-11-16 20:48:32

お礼

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;

投稿日時 - 2008-11-16 22:31:20

あなたにオススメの質問