カテゴリー
ファイル操作

ファイルの行数を取得する方法

参考URL
http://www.tohoho-web.com/lng/199912/99120066.htm

以下のように4種類考えられる


[ファイルの行数を得る方法] ALL : ファイル全体を変数に読み込んで、改行の数を数える
ARRAY : ファイル全体を配列に読み込んで、配列の要素数を数える
STEP : ファイルを一行ずつ読んで、行数を数える
WC : UNIX の wc コマンドを使う(wc --lines ファイル名)

これらのベンチマークをとるスクリプトを
http://www.aleph.co.jp/~fujiwara/perl/lc.pl
に置いてあります。

[行数を数える対象のファイル]
lc.pl (46 lines , 739 bytes)
jcode.pl (733 lines, 19900 bytes)
test.txt (17989 lines, 9229902 bytes)

[結果]
(Linux 2.2.13, Perl5.005_03, K6-2 400, 128MB RAM)

$ perl lc.pl lc.pl 10000
Benchmark: timing 10000 iterations of ALL, ARRAY, STEP, WC...
ALL: 2 wallclock secs ( 0.77 usr + 0.73 sys = 1.50 CPU)
ARRAY: 5 wallclock secs ( 4.24 usr + 1.00 sys = 5.24 CPU)
STEP: 3 wallclock secs ( 2.98 usr + 0.53 sys = 3.51 CPU)
WC: 75 wallclock secs ( 2.15 usr 6.05 sys + 36.30 cusr 30.17 csys = 0.00 CPU)

$ perl lc.pl jcode.pl 1000
Benchmark: timing 1000 iterations of ALL, ARRAY, STEP, WC...
ALL: 1 wallclock secs ( 0.53 usr + 0.23 sys = 0.76 CPU)
ARRAY: 8 wallclock secs ( 7.94 usr + 0.24 sys = 8.18 CPU)
STEP: 6 wallclock secs ( 5.22 usr + 0.15 sys = 5.37 CPU)
WC: 8 wallclock secs ( 0.14 usr 1.86 sys + 3.64 cusr 2.37 csys = 0.00 CPU)

$ perl lc.pl test.txt 100
Benchmark: timing 100 iterations of ALL, ARRAY, STEP, WC...
ALL: 3 wallclock secs ( 2.61 usr + 0.81 sys = 3.42 CPU)
ARRAY: 30 wallclock secs (29.12 usr + 0.89 sys = 30.01 CPU)
STEP: 21 wallclock secs (20.58 usr + 0.49 sys = 21.07 CPU)
WC: 2 wallclock secs ( 0.01 usr 0.19 sys + 0.94 cusr 1.27 csys = 0.00 CPU)

[講評(笑)]
意外に ALL が早いです。 WC は、プロセス起動のオーバーヘッドのためか、
ファイルが小さい場合は不利ですが、ファイルが大きくなると
高速ですね(...餅は餅屋)。

この結果だけ見るなら、ファイルが比較的小さいなら ALL 、
大きいなら WC 、メモリ消費をできるだけ押えたいなら STEP が
良いようです。

ファイルの行数を取得する一番いい方法は?(Perl5)

[上に] [前に] [次に]
CZ 1999/12/04(土) 19:11:28
for ($i = 0; $i <= $#data; $i++) {
print << "END";
ファイル名: $data[$i][0]
行数: $data[$i][1]
最初の行: $data[$i][2]
END
}

上のような配列@dataを得るために一番効率が良く処理が早いのはどういう方法でしょうか。ファイル中の行数を調べる段階で悩んでいます。

自分で幾つか考えてみましたが、どれがいいでしょう。あるいは他にいい方法はないでしょうか。

Perl5を使っています。@filesにはファイル名のリストが入っています。

(1) 一行ずつ読み込む方法。
for ($i = 0; $i <= $#files; $i++) {
open(READ, "$files[$i]");
$data[$i][0] = $files[$i];
$data[$i][1] = 0;
while () {
# 最初の行だけ記録する
if ($data[$i][2] eq '') { $data[$i][2] = $_; }
$data[$i][1]++;
}
close(READ);
}

(2) ファイルの内容を一度に読み込み、改行の数で行数を判定する方法。
for ($i = 0; $i <= $#files; $i++) {
open(READ, "$files[$i]");
$data[$i][0] = $files[$i];
read (READ, $str, (-s "$files[$i]"));
$data[$i][2] = substr($str, 0, index($str, "\n"));
$data[$i][1] = ($str =~ s|\n||g);
close(READ);
}

(3) ファイルの内容を一度に配列に読み込む方法。
for ($i = 0; $i <= $#files; $i++) {
open(READ, "$files[$i]");
$data[$i][0] = $files[$i];
@str = ;
$data[$i][1] = $#str + 1;
$data[$i][2] = $str[0];
close(READ);
}

(2)と(3)はファイルの内容を一度に読み込んでしまうので負荷が大きいと思います。かといって(1)は遅そうで……。
CZ 1999/12/04(土) 19:28:47
(2)の
> $data[$i][2] = substr($str, 0, index($str, "\n"));

$data[$i][2] = substr($str, 0, index($str, "\n") + 1);
の間違いでした。
猫 1999/12/04(土) 23:59:56
この件だけの為に、ベンチマークを取ったわけではありませんが、私の(浅い)経験上では、1の方法が負荷も少なく、速度も速かったように思います。
CZ 1999/12/06(月) 11:01:27
[[解決]]
なるほど。確かにそのようですね。最近の発言を眺めていたら、過去ログ ../199906/99060273.htm が紹介されていましたが、どんなに小さいファイルでも一気に読み込むより一行ずつ読み込む方が早そうです。ありがとうございました。
CZ-634C TN + CZ-614D TN 1999/12/06(月) 11:42:33
済のところ失礼しますm(__)m
UNIXでバッククォートが使えるならWCを使うって手もあります。
andi 1999/12/06(月) 12:45:58
ついでに質問させて下さい!
use Bentchmark;
を使用したのですが、
「274行目のtimesを実行できない。」
って言われちゃいました。
Win95+AnHTTPd+Perl5じゃぁ
無理なんでしょうか?
ふじ 1999/12/06(月) 15:55:44
>「274行目のtimesを実行できない。」
>って言われちゃいました。

Perl5 のバージョンは?(コマンドラインで perl -v )
確か古いのでは使えなかった気が。
#最新の Active Perl だと大丈夫です。
J.Naka 1999/12/06(月) 17:16:52
199906/99060273.txt のスクリプトを perl -wc で走らすと、以下のメッセージが出ます。
>Name "main::t" used only once: possible typo at FujiCodeORG.pl line 27.

バージョンは、 version 5.003_07です。何処がおかしいのでしょうか?


サーバー上で走らすPERLスクリプトは、なるだけ一行毎処理をするが良いと解かりましたが、ローカル上のコンピューター(WIN95)占有状態でも配列等の使用はスピードという点ではメリットないのでしょうか?

ここいらを上記のスクリプトで確かめてみたいところです。

もしかしてNT用ライブラリだったら精度落ちますがtime()関数当たりで代用してみようかと思ってます。(実際やってみましたが、例により何故か動かん(笑))

#しかし配列処理がスピード的にはデメリットになるとは驚きです(*_*)
ふじ 1999/12/06(月) 18:39:05
> 199906/99060273.txt のスクリプトを perl -wc で走らすと、以下のメッセージが出ます。
> >Name "main::t" used only once: possible typo at FujiCodeORG.pl line 27.
>バージョンは、 version 5.003_07です。何処がおかしいのでしょうか?

それは警告です。(w オプション指定しているから)
@t てのをスクリプト内で一度しか使ってないから警告されているんですけど。

#私も余りよく Benchmark.pm を理解してないけど、
#timethese を使うためのダミー、てことでいいのかな。

スクリプト自体は動きませんでしたか?
J.Naka 1999/12/06(月) 19:58:57
スクリプト動きませんです。
エラーメッセージです。意味が良く解かりません(^^;

>Benchmark: timing 1000 iterations of ALL , STEP...
>times not implemented at F:\Perl\lib/Benchmark.pm line 274.

#OSはWIN95です。
ふじ 1999/12/06(月) 21:50:03
>スクリプト動きませんです。
とりあえず Win95 + ActivePerl 522 では動きました。

#Perl5 for Win32 Build316 では同様のエラーが出て動かない、
#という話が以前 CGI-ML で流れてました。それと同じかと。
J.Naka 1999/12/06(月) 22:10:56
ども! ふじさん。動作するのはActiveが冠してあるやつなんですね、、、怪しいバージョンだ(笑)

えっと最後のお願いなんですが、199906/99060273.txt のサンプルコードの配列一気読み込みの配列をサブルーチンに入る前にあらかじめ固定領域確保をやってもらえないでしょうか? 
図々しいお願いは重々に承知してますんで、超お暇で気がごっつう向いた時で結構でおます(^^)
ふじ 1999/12/07(火) 02:41:07
>固定領域確保
それをやっても、(あのスクリプトの場合)
その恩恵を受けるのは何回もループする最初の1回では。
# 効率よく領域確保するためには、あらかじめファイルの大きさ、
# 行数、各行のバイト数を調べないといけないし、それやるぐらいなら
# 突っ込んじゃえ、って思うんですが (^^;

で、話が最初の話題からそれているので、今までの経緯を参考にまとめます。

[ファイルの行数を得る方法]

ALL : ファイル全体を変数に読み込んで、改行の数を数える
ARRAY : ファイル全体を配列に読み込んで、配列の要素数を数える
STEP : ファイルを一行ずつ読んで、行数を数える
WC : UNIX の wc コマンドを使う(wc --lines ファイル名)

これらのベンチマークをとるスクリプトを
http://www.aleph.co.jp/~fujiwara/perl/lc.pl
に置いてあります。

[行数を数える対象のファイル]
lc.pl (46 lines , 739 bytes)
jcode.pl (733 lines, 19900 bytes)
test.txt (17989 lines, 9229902 bytes)

[結果]
(Linux 2.2.13, Perl5.005_03, K6-2 400, 128MB RAM)

$ perl lc.pl lc.pl 10000
Benchmark: timing 10000 iterations of ALL, ARRAY, STEP, WC...
ALL: 2 wallclock secs ( 0.77 usr + 0.73 sys = 1.50 CPU)
ARRAY: 5 wallclock secs ( 4.24 usr + 1.00 sys = 5.24 CPU)
STEP: 3 wallclock secs ( 2.98 usr + 0.53 sys = 3.51 CPU)
WC: 75 wallclock secs ( 2.15 usr 6.05 sys + 36.30 cusr 30.17 csys = 0.00 CPU)

$ perl lc.pl jcode.pl 1000
Benchmark: timing 1000 iterations of ALL, ARRAY, STEP, WC...
ALL: 1 wallclock secs ( 0.53 usr + 0.23 sys = 0.76 CPU)
ARRAY: 8 wallclock secs ( 7.94 usr + 0.24 sys = 8.18 CPU)
STEP: 6 wallclock secs ( 5.22 usr + 0.15 sys = 5.37 CPU)
WC: 8 wallclock secs ( 0.14 usr 1.86 sys + 3.64 cusr 2.37 csys = 0.00 CPU)

$ perl lc.pl test.txt 100
Benchmark: timing 100 iterations of ALL, ARRAY, STEP, WC...
ALL: 3 wallclock secs ( 2.61 usr + 0.81 sys = 3.42 CPU)
ARRAY: 30 wallclock secs (29.12 usr + 0.89 sys = 30.01 CPU)
STEP: 21 wallclock secs (20.58 usr + 0.49 sys = 21.07 CPU)
WC: 2 wallclock secs ( 0.01 usr 0.19 sys + 0.94 cusr 1.27 csys = 0.00 CPU)

[講評(笑)]
意外に ALL が早いです。 WC は、プロセス起動のオーバーヘッドのためか、
ファイルが小さい場合は不利ですが、ファイルが大きくなると
高速ですね(...餅は餅屋)。

この結果だけ見るなら、ファイルが比較的小さいなら ALL 、
大きいなら WC 、メモリ消費をできるだけ押えたいなら STEP が
良いようです。

以下全ログ

カテゴリー
perl

use strict; でperl作成するべき

宣言していない変数を使用不可にする

Perl では C のように変数を宣言する必要がないので必要性がもうひとつはっきりしません。

use strict; が宣言されていると、変数がmy で宣言したプライベート変数か、パッケージ名を含めて完全に記述された変数しか使えなくなります。

不正な変数名の使用によるクラッシュを避けるためです。

このようにコンパイルに影響をあたえるモジュールを pragmatic module といいます。


mod_perlやSpeedyCGIなんかを使う時に誤動作を防ぐためにuseしてプライベート変数化しておくのに便利かも

カテゴリー
perl

sjisの罠

perlの正規表現などのマッチングを行うとエラーになる事がある。

エラーメッセージ
Unmatched [ in regex; marked by~


どうやらsjis(SHIFT-JIS)でのみ起こる現象のようだ。
対象の文字列や変数の中に文字化けする文字(表や申など)があると起こる現象のようです。


このページが参考になりました。
http://www.nishishi.com/blog/2006/02/unmatched_in_re.html

カテゴリー
perl

CGI標準入出力ライブラリ stdio.pl

perlスクリプトを作成する上で面倒なのがフォームのデコードやメール送信時に文字コードをエンコードする…など。

他にもよくつかうサブルーチンをいろいろ詰め込んだライブラリ「stdio.pl」を使えばフォームデータのデコード処理、クッキー入出力、ファイルロック等たった1行で書く事が出来ます。

使用するにはstdio.plを呼び出す必要があります。
使用するスクリプトとstdio.plを同じディレクトリに設置する場合はスクリプト内に

require 'stdio.pl';

と書くだけです。
以下のサイトから入手出来ます。


WEB POWER
http://www.webpower.jp/
WEB POWER「stdio.pl」
http://www.webpower.jp/websofts/others/libraries/stdio/

カテゴリー
perl ファイル操作

flock()を使った強固な排他処理(ファイルロック)

perlで排他処理(ファイルロック)を行う場合様々な方法があります。

昔のレンタルサーバの場合はflock()が使えない環境などがあり、
symlinkやmkdirを使った処理が多くありました。

しかし、スピード・信頼性に優れていて、現代どこのサーバ(Unix系の)でも使えるであろうflockを使わない手はないと思います。

flockなんか壊れる、信頼しない…使い方は間違っていませんか?

まずは各ロック方式の特徴を

■ symlink
・遅い。
・ロックしたままの状態(ロック用シンボリックリンク)が残る可能性がある。
・サーバーによっては使えない場合がある。

■ mkdir
・遅い。
・ロックしたままの状態(ロック用ディレクトリ)が残る可能性がある。
・処理全体をロックする場合に便利。
・どの環境でも使用できる。

■ flock
・速い。
・アンロックし忘れが無い。

■サンプルソース
flockは完了するまで永遠と処理を待ち続けるため10秒のアラーム処理を入れ10秒以内に完了しなければタイムアウトとしました。
この処理は強力でまずファイルが壊れる事はありません。

このファイルロックを使い、10万PV/日でも壊れませんでした。
ただし、ロック処理の間に他のファイルを開く時にはファイルハンドル(サンプルでは"FILEHANDLE")を変える必要があります。

これを怠ると必ずファイルが壊れます。

#ロック処理開始
eval {
local $SIG{ALRM} = sub { die "timeout" };
alarm 10;#アラームを10秒に設定
#ログ読み込み&ロック
open(FILEHANDLE,"+<"); #読み書きモードでファイルオープン
flock(FILEHANDLE, 2);  #ロック確認 ロック
@data = <FILEHANDLE>; #@dataに読み込み
alarm 0;
};
alarm 0;#アラーム解除
if($@) {&error;}; # タイムアウト時の処理 別にsub errorが必要になる

#〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
#  ここにロック中に行う処理
#〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

seek(FILEHANDLE, 0, 0);  # ファイルポインタを先頭にセット
print FILEHANDLE @data;  # ファイルに書き込む
truncate(FILEHANDLE, tell(FILEHANDLE));  # ファイルサイズを書き込んだサイズにする
close (FILEHANDLE);  #closeで自動にロック解除