【最大10000ポイント】当たる!!質問投稿キャンペーン!

perl v5.10.0 built for i386-linux-thread-multi
OS: Fedora 9 (Linux localhost.localdomain 2.6.25.11-97.fc9.i686 #1 SMP Mon Jul 21 01:31:09 EDT 2008 i686 athlon i386 GNU/Linux)

巨大なテキストファイルの最終行を効率良く取得する方法を探しています。
検索を駆使して 6 日 6 晩試行錯誤したのですが、遂に見付けられなかった為、此所で質問させて頂きます。
私が知っている方法は以下の 3 つですが、何れも環境に依存するか、或いは効率が悪い等の理由で不完全です。

----------------
#!/usr/bin/perl

my $filename = './47GiB.txt';
my $file;

# 1.
print `tail -n 1 $filename`;

# 2.
open $file, $filename;
print +(<$file>)[- 1]; # print pop @{[<$file>]};
close $file;

# 3.
open $file, $filename;
my $pos = 0;
while (<$file>) {
$pos = tell $file unless eof $file;
}
seek $file, $pos, 0;
print <$file>;
close $file;
----------------

tail コマンドの様に、瞬時に最終行を読み出す方法は無いのでしょうか ?
御回答宜しくお願いします。

このQ&Aに関連する最新のQ&A

A 回答 (5件)

すみません, 「1回は読まないといけない」はウソです.


一度ファイルの最後までシークして, あとは
・適当な分だけ戻る
・read かなにかで読み込む
・改行があったら, そこからあとを「最後の行」とする
という感じでいけると思います. これだと実行時間はファイル全体の大きさに関係なく, 最後の行の長さにのみ依存するはずです. 最後の行に改行があるかないかまで考えるとちょっと嫌ですが.
    • good
    • 0
この回答へのお礼

以下の様なコードを書いて見ました。

----------------
use feature qw(:5.10);

my $filename = './47GiB.txt';
my $final;

open my $file, $filename or die "$filename: $!\n";

for (my $i = - 2;; $i --) {
last if eof $file;
seek $file, $i, 2;
my $line = <$file>;
next unless defined $line;
chomp $line;
last if $line eq '';
$final = $line;
}

say $final;
close $file;
----------------

最終行が空行の場合に $final は空文字列に成るべきかも知れませんが、一先ず実用上は問題無さそうです。
御回答有難う御座いました。

お礼日時:2008/08/08 20:52

File::Readbacwards


を使うってのは駄目なんだろうか.
seekして最後から探してくれるCPANモジュール.
PurePerlだし,必要な処理だけ移植するのも
難しくなさそう.
PerlHacksで紹介されてるくらいだから
きっと便利なものでしょう.
    • good
    • 0
この回答へのお礼

File::ReadBackwards を使う事にします。
御回答有難う御座いました。
大変貴重なモジュールを御紹介頂き、重ねて感謝申し上げます。

http://search.cpan.org/dist/File-ReadBackwards/R …
http://perldoc.jp/docs/modules/File-ReadBackward …

----------------
use File::ReadBackwards;
print File::ReadBackwards->new('./47GiB.txt')->readline;

お礼日時:2008/08/10 12:23

ファイルの最後から指定行数を取得


http://www.din.or.jp/~ohzaki/perl.htm#File_Tail

アルゴリズムとしてはANo.3と同じです。
    • good
    • 0
この回答へのお礼

非常に参考に成りました。
御回答有難う御座います。

お礼日時:2008/08/10 12:16

「最終行」を得るためには, どうしてもファイルを 1回は読む必要があります.


だったら素直に
open(my $fh, $filename);
my $lastline;
$lastline = $_ while <$fh>;
close $fh;
print $lastline;
でいいと思うんだけど, 気のせい?

この回答への補足

御回答有難う御座います。
御提示頂いたコードを見て居て閃きました。
続きます。

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

こんなのありましたけど(参考URL参照)。


2. が単純な形で最も効率がいいように思いますけどなんかダメ
だったんでしょうか。

参考URL:http://tuka.s12.xrea.com/index.xcg?p=Perl#p5

この回答への補足

御回答誠に有難う御座います。
質問文の 3. のコードは、其のサイトのコードから着想を得て書いた物です。然し、速度が遅いです。
現在手元に件のテキストファイルが有るのですが、最終行を取得するのに可也の時間が掛かって仕舞って居ます。
より速いアルゴリズムはないのでしょうか ?

補足日時:2008/08/08 17:24
    • good
    • 0

このQ&Aに関連する人気のQ&A

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

このQ&Aを見た人はこんなQ&Aも見ています

このQ&Aを見た人が検索しているワード

このQ&Aと関連する良く見られている質問

QPerlで特定行から特定行までを抜き出したい

皆さんのお知恵をお貸し頂ければ幸いです。

Perlで以下のようなことをしたいと考えています。
例えば、次のようなテキストファイルがあったとします。

example.log
==================================
aaaa
hogehoge
test
okok
perl
script
==================================

上記ファイルを読み込んで、「hogehoge」から「perl」の間に挟まれた行だけ抜き出したいのです。
イメージとしては、読み込んだファイルを配列に入れて、一行づつ読ませ、キーワード「hogehoge」が現れたらそこでフラグを立て、それ以降の行を表示し、キーワード「perl」が現れた時点で表示を止めるという処理になるのかな?と思っています。

このような場合、どういう風にすればいいのでしょうか?
恐れ入りますが、ご教授頂ければ幸いです。

それでは、どうぞよろしくお願い致します。

Aベストアンサー

> 一行づつ読ませ、キーワード「hogehoge」が現れたらそこでフラグを立て、それ以降の行を表示し、キーワード「perl」が現れた時点で表示を止めるという処理になるのかな?と思っています。

それでいいと思いますよ?これをそのままコード化すると、こんな感じでしょうか。(No.1さんのとはちょっと結果が違います。)

open FH, "example.log" or die $!;
$flag = 0;
while ($data = <FH>) {
  chomp $data;
  if  ($data eq "hogehoge") { $flag = 1 }
  elsif ($data eq "perl")    { $flag = 0 }
  elsif ($flag) { print "$data\n" }
}
close FH;

で、もっと略したいPerlな人だとこんな感じ。Perl独特の記法がふんだんに使われているので、勉強するには不向きかもしれませんが^^;

open FH, "example.log" or die $!;
while (<FH>) {
  print if /^hogehoge$/ .. /^perl$/ and !/^(?:hogehoge|perl)$/;
}
close FH;

※インデントに全角空白を使っているので、コピーする場合はタブなどに置換して下さい。

> 一行づつ読ませ、キーワード「hogehoge」が現れたらそこでフラグを立て、それ以降の行を表示し、キーワード「perl」が現れた時点で表示を止めるという処理になるのかな?と思っています。

それでいいと思いますよ?これをそのままコード化すると、こんな感じでしょうか。(No.1さんのとはちょっと結果が違います。)

open FH, "example.log" or die $!;
$flag = 0;
while ($data = <FH>) {
  chomp $data;
  if  ($data eq "hogehoge") { $flag = 1 }
  elsif ($data eq "perl")    { $fl...続きを読む

Qファイルの行数取得

超初心者です。

いま、表計算的なスクリプトを記述しています。

あるファイルの行数を取得する関数ってあるんでしょうか?

ファイルに記述されている数値を足したり引いたりするのですが、forを使っての計算の際にファイルの行数が必要となりました。

Aベストアンサー

Perlにですね。ないはずです。
行数とはファイルに書かれた改行文字の個数ということなので
実際にファイルを全て読み込まないと行数はわかりません。
以下のように色々な方法があると思います

#### 単純な例
$a = 0;
open FD, "<file.txt" || die $!;
while (<FD>) {
$a++;
}
close FD;
print "行数:$a\n";

### 少しマニアックな方法
open FD, "<file.txt" || die $!;
@a = <FD>;
close FD;
print "行数:" . ($#a + 1) + "\n";


### 反則的方法(外部コマンド) ... UNIXの場合
print "行数:" . `wc -l file.txt` . "\n";

Q数値かどうかの判定方法

$aに代入されているものが数値かどうかを判定するにはどのようにしたらよいのでしょうか?

Aベストアンサー

$a =~ /^[0-9]*$/
上記の場合、*は「直前のパターンの0回以上の繰り返し」の意味なので、0から9がなくても、つまり$aが空でもマッチしてしまいます。
なので、
$a =~ /^[0-9]+$/
としましょう。
(+は「直前のパターンの1回以上の繰り返し」)
また、0-9は\dで表すこともできるので
$a =~ /^\d+$/
と書くこともできます。

Q複数ファイルの読み込みについて

perl初心者です。

あるディレクトリから拡張子がdataであるファイルを全て読み込みたいのですが、方法がわかりません。
cshで書くと
foreach arg (*.data)
コマンド $arg

のようになりますが、perlだと
foreach $arg (@arg){
コマンド $arg

となりますよね?
引数がリストなのでよくわかりません。
そもそもperlではできないのでしょうか?


それともう一点ですが、ファイルオープンするときに
foreachループの中で
open(FILE, "$arg");
とすることは可能ですか?
上の質問と組み合わせて全てのファイルを開いて作業を行いたいので。

説明が下手ですいません。補足しますのでよろしくお願いします。

Aベストアンサー

while(<*.data>)
{
## $_には、*.DATAなファイル名が格納されている。
open(F,"$_"); ##openする。
while(<F>)
{
##読み出された内容が$_に格納されている。
print $_; ##出力してみる。
}
}

というのが最短コーディングです。

Qsedの置換文字に変数を使用したいのですが・・・

あるファイルの特定の文字を変換し、上書きをする処理を行いたいのですが、sedの置換文字に変数が渡せなくて困っています。

例:
X="a"
Y="b"
echo test.txt | sed 's/${X}/${Y/g}' >test.txt

sedでは置換文字に${X}といった変数を使用することはできないのでしょうか?

Aベストアンサー

' ・・・' で囲まれた中の$はそのままドルマークです。変数展開をするなら、'・・・'で囲んではいけません。

何も囲まないか、"・・・"で囲むかです。

Qファイルの最後の行から表示させる(最新情報を5回分だけ表示)

ファイルの最後に1行追加し、6行以上になるとファイルの先頭を1行削除。
表示は最後の行から行うスクリプトを作りたいのです。
新しい情報を5回分だけ表示する様にしたかったのですが、余りにも力技なのでもう少しスマートにやる方法を教えてもらえませんか?

----sort.txt----
1a
2b
3c
4d
----------------





#!/usr/bin/perl
$fname = "sort.txt";
print "Content-type: text/html;\n\n";
$aaa="5e";
open(OUT, ">>$fname");
flock(OUT, LOCK_EX);
print OUT "$aaa\n";
flock(OUT, LOCK_NB);
close(OUT);
open(IN, "$fname");
$cnt = "0";
while($line = <IN>){
$a[$cnt] = "$line";
$cnt++;
}
close(IN);
if($cnt > 5){
open(OUT, ">$fname");
flock(OUT, LOCK_EX);
$cnt = "0";
while($cnt < 6){
if($cnt > 0){
print OUT "$a[$cnt]";
}
$cnt++;
}
flock(OUT, LOCK_NB);
close(OUT);
}
$cnt = "0";
open(OUT, "$fname");
@buff = <OUT>;
close(OUT);
foreach $line ( reverse @buff ){
$cnt++;
print "$line<br>\n";
}
exit();

ファイルの最後に1行追加し、6行以上になるとファイルの先頭を1行削除。
表示は最後の行から行うスクリプトを作りたいのです。
新しい情報を5回分だけ表示する様にしたかったのですが、余りにも力技なのでもう少しスマートにやる方法を教えてもらえませんか?

----sort.txt----
1a
2b
3c
4d
----------------





#!/usr/bin/perl
$fname = "sort.txt";
print "Content-type: text/html;\n\n";
$aaa="5e";
open(OUT, ">>$fname");
flock(OUT, LOCK_EX);
print OUT "$aaa\n";
flock(OUT...続きを読む

Aベストアンサー

flockによる排他制御部分は省略します.

#!/usr/bin/perl

print "Content-type: text/html;\n\n";
my $fname = "sort.txt";
my $aaa = "5e";
my @list = ();

#一旦配列に読み込み
open(IN, "$fname");
@list = <IN>;
close(IN);

# 末尾に要素追加
push(@list, $aaa);

# 6行以上なら先頭を削除
# $#list は配列の最後の添え字を表すので
# $#list+1 は配列のサイズ = 現在の行数
if($#list+1 >= 6) { shift(@line); }


#保存
open(OUT, ">$fname");
print OUT @list;
close(OUT);

#表示(@listの使いまわし)
foreach(reverse @list){
 print("$_<br>");
}
exit;
-----------

ですかね。一度配列に読み込んじゃってから追加削除操作をしたほうがスマートかなぁと思います。

flockによる排他制御部分は省略します.

#!/usr/bin/perl

print "Content-type: text/html;\n\n";
my $fname = "sort.txt";
my $aaa = "5e";
my @list = ();

#一旦配列に読み込み
open(IN, "$fname");
@list = <IN>;
close(IN);

# 末尾に要素追加
push(@list, $aaa);

# 6行以上なら先頭を削除
# $#list は配列の最後の添え字を表すので
# $#list+1 は配列のサイズ = 現在の行数
if($#list+1 >= 6) { shift(@line); }


#保存
open(OUT, ">$fname");
print OUT @list;
close(OUT);
...続きを読む

QPerlで行頭にある文字が含まれている行を全部削除して詰めたい

perl初心者です。以下のようにデータがならんでいる時、
test111 aaaaaaaaabbbbbbbbcccccc
test112 aaaaccccabbbbbbbbcccccc
test113 aaaaccaaabbbbbbbbcccccc
test114 acccaaaaabbbbbbbbcccccc

test111 aacaaaaaabbbbbbbbcccccc
test112 accaaaaaabbbbbbbbcccccc
test113 aaacccaaabbbbbbbbcccccc
test114 aaaaaccaabbbbbbbbcccccc

test112の行だけ削除して、さらにそこを詰めたい時のスクリプトを作成しています。

途中からわかりません。
行を削除する関数が調べても見つからないのです。

#!/usr/bin/perl ;
open(IN, "test.doc") or die ;

open(OUT, ">testout.doc");

while(<IN>) {
chomp ;
if (/(\S+)/) {

$name = $1 ;

if ($name =~ /^test112(\S+)/) {
#ここでマッチさせて、一気に行を削除して、しかも行を詰めたいのですが

;

}
print OUT " \n" ;
}
}
close (IN) ;
close (OUT) ;

大変困っております。宜しくお願いします。

perl初心者です。以下のようにデータがならんでいる時、
test111 aaaaaaaaabbbbbbbbcccccc
test112 aaaaccccabbbbbbbbcccccc
test113 aaaaccaaabbbbbbbbcccccc
test114 acccaaaaabbbbbbbbcccccc

test111 aacaaaaaabbbbbbbbcccccc
test112 accaaaaaabbbbbbbbcccccc
test113 aaacccaaabbbbbbbbcccccc
test114 aaaaaccaabbbbbbbbcccccc

test112の行だけ削除して、さらにそこを詰めたい時のスクリプトを作成しています。

途中からわかりません。
行を削除する関数が調べても見つからないのです。

...続きを読む

Aベストアンサー

一致しなかったときのみ出力するということで、

while(<IN>){
unless(/^test112/){
print OUT;
}
}

更に簡単に書くと、
while(<IN>){
print OUT unless(/^test112/);
}
となります。

削除にこだわるなら、
while(<IN>){
s/^test112\s.+//;
print OUT;
}
あたりでしょうか。

もしくは、明示的に削除したいなら、
while(<IN>){
if(/^test112\s.+/){
$_ = "";
}
print OUT;
}
というふうに、カラの文字列を代入してやるのも、値を削除するときの常套手段ですね。

Qひとつの命令を複数行に記述

検索してもあまり解説見かけないなぁと思うのですが、(どこが本家かわからないのでとりあえず放置)

VBでいう_に該当するものは何ですか?
ソースが長くなって見にくくなっているので、対処したいのですが。

hoge = "じゅげむじゅげむごごうのすりきれ" _ '←次の行に送る
& "かいじゃりすいぎょのうんらいまつふうらいまつすいぎょうまつ" 

Aベストアンサー

Perlは、VBと異なり行の概念がありません。
VBではステートメント区切り子が存在しないため、改行がステートメントの区切りとして扱われ、例外的につなげるときに「_」を使うわけですが、
Perlの場合はステートメント区切り子セミコロン「;」がステートメントの区切りになっていますので、セミコロンを打たない限り、何行に分かれてもひとつのステートメントとして扱われます。
したがって、回答としては「そのまま改行してOK」です。
上記の例なら、

$hoge = "じゅげむじゅげむごごうのすりきれ" .
"かいじゃりすいぎょのうんらいまつふうらいまつすいぎょうまつ";

という感じです。
当然ながら、文字列中での改行はダメなので、上記のようにいったん「"」を閉じて、文字列結合演算子「.」で接続することに成ります。

Q特定の改行コードだけ削除する

CSVファイルを変換するPGを書きたいのですが、
変換したいCSVには改行コード\nと\r\nが混在しています。

改行コード\r\nはそのままにして
\nだけ削除したいのですが
どのように書けばよろしいのでしょうか。

Perlは5.12.2です。

よろしくお願いします。

Aベストアンサー

0x0d→0x0d
0x0d0x0a→0x0d0x0a
0x0a→削除
なら
binmode STDIN;
binmode STDOUT;
while(<STDIN>){
if ( /\r\n$/ ){
}
elsif ( /\n$/ ){
chomp;
}
print $_;
}
として「foo.pl」
perl foo.pl < 入力ファイル > 出力ファイル

Qgrepで検索文字列が完全一致した行だけ取り出す方法

grepの文字列検索で検索文字列が単語として、完全一致した行だけ取り出す方法はないでしょうか?

通常は
grep hoge hoge.txt

と打つと、hogeが含まれる行が出力されますが、今回は含まれる行ではなくて完全に文字列が一致した行だけ取り出したいのです。

例えばhoge.txtの中に
cc ghoge
kkl hogem
jjll hoge
という3行があったとしたら最後の行でhogeという文字が空白で区切られた行だけ取り出したいのです。

何かよい方法があれば教えてください

Aベストアンサー

-w オプションじゃだめですか?

参考URL:http://www.linux.or.jp/JM/html/GNU_grep/man1/grep.1.html


人気Q&Aランキング