その後、Getopt::Stdモジュールを利用して、いくつかの便利そうなオプションを追加しました。
hオプションでヘルプを出力
sオプションでユーザーエージェントに「Mac」のある行を出力してみました
#!/usr/bin/perl -w
use strict;
use Getopt::Std;
# ログファイルの整理プログラム log.pl ver. 1.3
# 入力するログファイルのフォーマット
# host ident authuser date "request" status bytes "referer"
# "user-agent"\n
# date = [day/month/year:hour:minute:second zone]
# day 2桁, month 3文字, year 4桁, hour 2桁, minute 2桁, second 2桁
# zone (+|-)4桁
# request = command pass HTTP/*.*
# status 3桁
# 初期設定
# 回数をカウントするリクエストのパスの末尾(-p|r|h|uの場合に有効)
my @count = qw( / .html .xml .rdf );
my %counter = ();
my %opt = ();
# オプション設定
getopts ('pruaPUs:S:c:l:h', \%opt);
if (! $opt{p} && ! $opt{r} && ! $opt{u} && ! $opt{a} &&
! $opt{P} && ! $opt{U}) {
$opt{p} = 1; # 出力の標準
}
if (! $opt{c} || $opt{c} < 20 || $opt{c} > 256) {
$opt{c} = 79; # 出力桁の標準値
}
if (! $opt{l} || $opt{l} < 1) {
$opt{l} = -1; # 出力行の標準値
}
if ($opt{s}) {
print "-s search pattern: ", $opt{s}, "\n";
}
if ($opt{S}) {
print "-S search pattern: ", $opt{S}, "\n";
}
if ($opt{h}) {
print "ログファイルの整理プログラム ver. 1.3\n" .
"log.pl [-p|r|u|a|P|U]\n" .
" [-s search_pattern] [-S search_pattern]\n" .
" [-c output_columns] [-l output_lines] [-h]\n\n" .
"出力オプション\n" .
"p アクセス先を回数でソート(passの前半を出力)\n" .
" オプション指定が無い場合の出力の標準\n" .
"r アクセス元を回数でソート(refererの前半を出力)\n" .
"u アクセス者を回数でソート(hostの後半を出力)\n" .
"a ブラウザ を回数でソート(user-agentの前半を出力)\n" .
"P クライアントエラーを出したアクセス先を回数でソート\n" .
"U クライアントエラーを出したアクセス者を回数でソート\n" .
"s search_pattern 正規表現のパターンにマッチする場合出力\n" .
"S search_pattern 正規表現のパターンにマッチしない場合出力\n" .
"c output_columns 指定桁まで出力(値は20..256,標準は79桁出力)\n" .
"l output_lines 指定行まで出力(値は1以上, 標準は全て出力)\n" .
"h ヘルプを出力\n";
exit 0;
}
# 標準入力からのログファイルの読み込み
foreach (<STDIN>) {
my($host, $request, $status, $referer, $agent) =
/^(\S+).+\[\S+ \S+\] "([^"]+)" (\d{3}) .+ "([^"]+)" "([^"]+)"\n/;
# 該当行の抽出
my $out = 0;
if ($opt{p} || $opt{r} || $opt{u} || $opt{a}) { # -p|r|u|aの場合
foreach (@count) {
# パスの末尾が合致し、かつstatusが200番の行を抽出
if (($request =~ /\S+ .*$_ \S+$/) && ($status == 200)) {
$out = 1;
last;
}
}
} elsif ($opt{P} || $opt{U}) { # -P|Uの場合
if ($status =~ /4\d\d/) { # statusが400番台の行を抽出
$out = 1;
}
}
# ハッシュに該当行を格納
if ($out == 1) {
my $key ="";
if ($opt{p} || $opt{P}) { # -p|Pの場合
$request =~ /^\S+ (\S+) \S+$/;
$key = $1;
} elsif ($opt{r}) { # -rの場合
$key = $referer;
} elsif ($opt{u} || $opt{U}) { # -u|Uの場合
$key = $host;
} elsif ($opt{a}) { # -aの場合
$key = $agent;
}
if ((! $opt{s} && ! $opt{S}) || # -s|Sオプションなし
($opt{s} && ! $opt{S} && $key =~ /$opt{s}/) || # -sの時
($opt{S} && ! $opt{s} && $key !~ /$opt{S}/) || # -Sの時
($opt{s} && $opt{S} && # -s-Sの時
$key =~ /$opt{s}/ && $key !~ /$opt{S}/)) {
if (exists $counter{$key}) {
$counter{$key}++; # キーがあればカウントアップ
} else {
$counter{$key} = 1; # キーがなければ作成
}
}
}
}
# 標準出力への書き出し
if (keys %counter > 0) { # %counter が空でなければ表示処理
# カウントの大きい順にソート
my @list = sort {
$counter{$b} <=> $counter{$a} or
$a cmp $b # カウントが同じ場合はキーでソート
} keys %counter;
if ($opt{l} != -1 && $opt{l} < @list) {
for (1..@list - $opt{l}) { # 不要な行を削除
pop @list;
}
}
# 出力
my $c_len = length $counter{$list[0]}; # カウントの文字数
my $a_len = $opt{c} - $c_len - 12; # キーの文字数
foreach (@list) {
my $star = "*" x int $counter{$_} / $counter{$list[0]} *
10 + 0.5;
my $access = $_;
if ($opt{u} || $opt{U}) { # -u|Uの場合は後半を出力
$access = substr($_, -$a_len, $a_len);
}
printf "%10s %" . $c_len . "d %-." . $a_len . "s\n",
$star, $counter{$_}, $access;
}
}
-sと-Sオプションの引数は正規表現で入力するのですが、上記プログラムは少し問題があります。例えば、「ne.jp」の正規表現は、メタキャラクタの「.」をにバックスラッシュを付けて、ne\.jp
とすれば良いのですが、このプログラムの場合はオプションを%optに代入するまでの間にバックスラッシュが1つ使われてしまうようで、ne\\.jp
としないと正常に動作しません。どこで使われてしまうのかな...(?_?;
ふと、正規表現の文字列を「"」や「'」で括ってみたところ、正常に動作するようになりました。なーんだ...(^_^;
Mozilla/x.0を含む行を除くと、ロボットっぽいものばかりに...