アプリ版:「スタンプのみでお礼する」機能のリリースについて

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

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

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

A 回答 (9件)

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



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

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

しかし「ある方」はねーだろ
    • good
    • 0
この回答へのお礼

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

「指定の行数目から行を抽出する」って, 意味わかんないよね.


でも, 単に 1つのファイルに出力するだけならプログラム内で出力ファイルを書くよりも外でリダイレクトさせた方が柔軟だろうとか, 5.10 ならスマートマッチ使えばいいのにとかは思う.
    • good
    • 0
この回答へのお礼

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

お礼日時:2008/11/16 22:04

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

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

お礼日時:2008/11/16 23:44

別に名前を出せという意味でもなくて、単に「別の質問で教えてもらった」


でいいんじゃなかろうかということです。

んで、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;

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

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

ディレクトリ内の各テキストファイルに対して処理するのはいいんですけど, 出力はどうしますか?


・各ファイルごとに (それなりなファイル名の) ファイルに出力する
・全てのファイルに対する出力を全部まとめて 1つにする
どっちにします?

この回答への補足

Tacosan様

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

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

補足日時:2008/11/17 07:26
    • good
    • 0

OK. あと問題になるのは, ここで出てくる「2個の入力ファイル」の関係.


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

この回答への補足

Tacosan様

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

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

以上の件了解しました.

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

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

補足日時:2008/11/17 12:36
    • good
    • 0
この回答へのお礼

ここ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

気になったこと:


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

Tacosan様

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

お礼日時:2008/11/19 20:47

例えば:


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

Tacosan様

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

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

お礼日時:2008/11/19 22:17

いや, とりあえず「どの質問に答えるか」ということについては「自分が答えられそうか」ということをまず考えて, その上でわりときまぐれですね.


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

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

お礼日時:2008/11/20 02:42

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!