2004年7月
2004年7月29日
Category : Terminal / Posted at 2004年7月29日 21:13
Perlの使い方が少々分かってきたので、LWP::SimpleとLWP::UserAgentモジュールを使ってみたくなり、CPANに登録されているlibwww-perl-5.800をインストールしてみました。
libwww-perl-5.800のreadmeによると、インストールには予めURI、MIME-Base64、HTML-Parser、libnet(後で分かったのですが、必要なのはNet::FTPモジュールのようです)、Digest-MD5がインストールされていなければならないようなので、Mac OS Xに標準でインストールされていないURIとHTML-Parserを先にインストールします。
● URI-1.31のインストール
$ curl -O http://www.perl.com/CPAN/modules/by-module/URI/URI-1.31.tar.gz
$ tar xzf URI-1.31.tar.gz
$ cd URI-1.31
$ perl Makefile.PL
$ make
$ make test
$ sudo make install
$ cd ..
● HTML-Tagset-3.03のインストール
HTML-Parser-3.36のインストールにはHTML::Tagsetモジュールが必要なので、先にHTML-Tagset-3.03をインストールします。
$ curl -O http://www.perl.com/CPAN/modules/by-module/HTML/HTML-Tagset-3.03.tar.gz
$ tar xzf HTML-Tagset-3.03.tar.gz
$ cd HTML-Tagset-3.03
$ perl Makefile.PL
$ make
$ make test
$ sudo make install
$ cd ..
● HTML-Parser-3.36のインストール
$ curl -O http://www.perl.com/CPAN/modules/by-module/HTML/HTML-Parser-3.36.tar.gz
$ tar xzf HTML-Parser-3.36.tar.gz
$ cd HTML-Parser-3.36
$ perl Makefile.PL
$ make
$ make test
$ sudo make install
$ cd ..
● libwww-perl-5.800のインストール
$ curl -O http://www.perl.com/CPAN/modules/by-module/LWP/libwww-perl-5.800.tar.gz
$ tar xzf libwww-perl-5.800.tar.gz
$ cd libwww-perl-5.800
$ perl Makefile.PL
$ make
make testの前に、apache-listing.tの下記の行を削除しないと、リンク切れのアドレスのためテスト時にエラーが出てしまいます。
$ vim t/live/apache-listing.t
"http://gump.covalent.net/jars/?C=N&O=D", この行を削除
削除後は普通に、
$ make test
$ sudo make install
$ cd ..
これでLWP::SimpleとLWP::UserAgentモジュールを含む多くのモジュールが/Library/Perl/5.8.1/以下にインストールされます。さて、インストールしたモジュールを使って、何を作ろうかな...(^_^;
2004年7月24日
Category : Terminal / Posted at 2004年7月24日 16:49
その後、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を含む行を除くと、ロボットっぽいものばかりに...
Category : Game / Posted at 2004年7月24日 10:12
その後NORMALでやっているのですが、これがムズい...(^_^; クリアまでの道はかなり遠そうです。
ところで、HALO(Mac版)の日本語版がMicrosoft game studiosから今年の秋に出るようです。HALO 2も出ないかなとちょっと期待...。

ここまで車で来てみました

このルートで戻るとすぐに海岸に着きます...(^_^;(本文とは関係ありませんw)
2004年7月11日
Category : Web / Posted at 2004年7月11日 01:05
シックス・アパートからMovable Type 3.0 日本語版ベータ2が出ていたので、早速ローカルにインストールして使ってみました。

投稿されたコメントを掲載前にチェックできます
以前の日本語版ベータでは、エラーが出ていて正常に動作しなかったのですが、今回のベータ2は特に問題ないようです。使ってみて気がついた点を上げてみると、
・「プロフィールの編集」のパスワード入力欄に文字を入れたときの動作が少し変(実害はありません)
・full-libなのに/docsにヘルプファイルが入っていない(2.661も同じ)
便利になり、色々と改良されているようなので、正式板になって落ち着いたらバージョンアップしたいと思います。
2004年7月10日
Category : Terminal / Posted at 2004年7月10日 21:10
ようやく、ハッシュの使い方とソートの仕方を覚えたので、データの出現頻度が高い順にソートして出力するブログラムに改良しました。
#!/usr/bin/perl -w
use strict;
# ログファイルの整理プログラム log.pl [-p|r|h|P|H] ver.1.2
# 入力するログファイルのフォーマット
# 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 アクセス先を回数でソート(標準)
# ********** 999999 pass(前半40文字)
# r アクセス元を回数でソート
# ********** 999999 referer(前半40文字)
# h アクセス者を回数でソート
# ********** 999999 host(後半40文字)
# P クライアントエラーを出したアクセス先を回数でソート
# ********** 999999 pass(前半40文字)
# H クライアントエラーを出したアクセス者を回数でソート
# ********** 999999 host(後半40文字)
# 回数をカウントするリクエストのパスの末尾(-p|r|hの場合に有効)
my @count = qw( / .html .xml .rdf );
my %counter = ();
my $option = '-p';
if (@ARGV) {
if ($ARGV[0] =~ /^-[prhPH]/) {
$option = shift @ARGV;
}
}
foreach (<STDIN>) {
/^(\S+).+\[\S+ \S+\] "([^"]+)" (\d{3}) .+ "([^"]+)" "[^"]+"\n/;
my $host = $1;
my $request = $2;
my $status = $3;
my $referer = $4;
my $key = '';
my $out = 0;
if ($option =~ /[prh]/) { # -p|r|hの場合
foreach (@count) {
if (($request =~ /\S+ .*$_ \S+$/) &&
($status == 200)) {
$out = 1;
last;
}
}
} elsif ($option =~ /[PH]/) { # -P|Hの場合
if ($status =~ /4\d\d/) {
$out = 1;
}
}
if ($out == 1) {
if ($option =~ /[pP]/) { # -p|Pの場合
$request =~ /^\S+ (\S+) \S+$/;
$key = $1;
} elsif ($option =~ /r/) { # -rの場合
$key = $referer;
} elsif ($option =~ /[hH]/) { # -h|Hの場合
$key = $host;
}
if (exists $counter{$key}) {
$counter{$key}++; # キーがあればカウントアップ
} else {
$counter{$key} = 1; # キーがなければ作成
}
}
}
my @list = sort { # カウントの大きい順にソート
$counter{$b} <=> $counter{$a} or
$a cmp $b # カウントが同じ場合はキーでソート
} keys %counter;
foreach (@list) { # 出力
my $star = '*' x int $counter{$_} / $counter{$list[0]} * 10 + 0.5;
my $access = $_;
if ($option =~ /[hH]/) { # アクセス者の場合は後半40文字
$access = substr($_, -40, 40);
}
printf "%10s %6d %-.40s\n", $star, $counter{$_}, $access;
}
C'S SERVER Personalのログファイルは、日毎にzipで圧縮されているので、zcat の出力をパイプで入力すると便利です。

7月のログファイルを入力してみました
なお、このプログラムのソートでは、出現頻度が同じ場合はキーの文字列を先頭からソートしています。本来、ソートの対象がアクセス者(host)の場合は、ドット(.)区切りのワード毎に後ろからソートすべきだと思いますが、使用上問題がないので簡略化しています。(一言でいうと手抜きですね...(^_^;)
2004年7月 5日
Category : Terminal / Posted at 2004年7月 5日 23:16
前回、「リンク元が同じものを1行にまとめるプログラム」というものを作ってみたのですが、一体どんな意味があるのかと思い考えてみたところ、あまり意味がないことに気づきました*1...(^_^;
そこで、refererをチェックするのではなく、requestをチェックして、「1アクセスを1行にまとめるプログラム」に改良しました。
#!/usr/bin/perl -w
use strict;
# ログファイルの整理プログラム log.pl [-dhqDHQ] ver.1.1
# オプション d:date, h:host, q:request を整形して出力
# D:date, H:host, Q:request を整形せずに出力
# 入力するログファイルのフォーマット
# 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 file-pass-name HTTP/*.*
# 出力するログファイルの標準フォーマット(-dhq)
# date host request
# date = day/month/year:hour:minute:second
# day 2桁, month 3文字, year 4桁, hour 2桁, minute 2桁, second 2桁 計20桁
# host 後半20文字
# request 前半37文字
my @delete = qw/ .jpg .gif .cgi .css .ico .sit .txt /; # 除外する拡張子名
my $option = '-dhq';
if (@ARGV) {
if ($ARGV[0] =~ /^-[dhqDHQ]/) {
$option = shift @ARGV;
}
}
foreach (<STDIN>) {
/(^\S+).+(\[\S+ \S+\]) "([^"]+)".+"[^"]+" "[^"]+"\n/;
my $host = $1;
my $date = $2;
my $request = $3;
# 除外する拡張子を持つファイルをリクエストしている行は出力しない
my $out = 1; # 初期値 = 出力する
foreach (@delete) {
if ($request =~ /$_/) {
$out = 0; # 出力しない
last;
}
}
# 出力
if ($out == 1) {
if ($option =~ /d/) { # 秒まで
print substr($date, 1, 20), ' ';
} elsif ($option =~ /D/) { # そのまま
print "$date ";
}
if ($option =~ /h/) { # 後半20文字
printf "%20.20s ", substr($host, -20, 20);
} elsif ($option =~ /H/) { # そのまま
print "$host ";
}
if ($option =~ /q/) { # 前半37文字
$request =~ /^\S+ (\S+) \S+/;
print substr($1, 0, 37);
} elsif ($option =~ /Q/) { # そのまま
print "\"", $request, "\"";
}
print "\n";
}
}
ただでさえPerlっぽい部分が少ないのに、さらに少なくなってしまいました。なお、@delete に除外する拡張子名(例えば.xml とか.rdf 等)を半角スペースで区切って追加すると、それらのファイルを要求している行が出力されなくなります。

前回と微妙に違いますな...w
2004年7月 4日
Category : Terminal / Posted at 2004年7月 4日 17:47
C'S SERVER Personalのサブドメインサービスはアクセスログを取得できるので、面白半分でエディタで開いてみたところ、あまりの量の多さにびっくり(これまで見たことなかったもので...(^_^;)。よく見ると、1ファイルの転送が1行で記録されており、トップページに1度アクセスしただけで20行程のログ...。こりゃ人間の見るものじゃないなと思い、リンク元が同じものを1行にまとめるプログラムを作ってみることにしました。
プログラムの作成には、文字列の扱いが簡単にできそうで、かつインターネットで広く使われているPerl*1を使うことにしました。早速、オライリー・ジャパンの初めてのPerl 第3版*2(通称「リャマ本」)を買ってきて、本を見ながら作ってみました。
#!/usr/bin/perl -w
use strict;
# ログファイルの整理プログラム log.pl [-dhrDHR] ver.1.0
# オプション d:date, h:host, r:referer を整形して出力
# D:date, H:host, R:referer を整形せずに出力
# 入力するログファイルのフォーマット
# 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桁
# 出力するログファイルの標準フォーマット(-dhr)
# date host referer
# date = day/month/year:hour:minute:second
# day 2桁, month 3文字, year 4桁, hour 2桁, minute 2桁, second 2桁 計20桁
# host 後半20文字
# referer 前半37文字,自分のサイトアドレスは / に置き換える
my $my_site = 'http://masaharu.la.coocan.jp/blog/'; # 自分のサイトアドレス
my $check = 10; # 遡ってチェックする行数
my @before_host = (' ') x $check;
my @before_referer = (' ') x $check;
my $option = '-dhr';
if (@ARGV) {
if ($ARGV[0] =~ /^-[dhrDHR]/) {
$option = shift @ARGV;
}
}
foreach (<STDIN>) {
/(^\S+).+(\[\S+ \S+\]) "[^"]+".+"([^"]+)" "[^"]+"\n/;
my $host = $1;
my $date = $2;
my $referer = $3;
# 遡ったチェック行数内で host と referer が同じ場合は出力しない
my $out = 1; # 初期値 = 出力する
foreach (0..$check - 1) {
if (($host eq $before_host[$_]) &&
($referer eq $before_referer[$_])) {
$out = 0;
last;
}
}
pop @before_host; # 配列の末尾を削除
pop @before_referer;
unshift @before_host, $host; # 配列の先頭に追加
unshift @before_referer, $referer;
# 出力
if ($out == 1) {
if ($option =~ /^-.*d/) { # 秒まで
print substr($date, 1, 20), ' ';
} elsif ($option =~ /^-.*D/) { # そのまま
print "$date ";
}
if ($option =~ /^-.*h/) { # 後半20文字
printf "%20.20s ", substr($host, -20, 20);
} elsif ($option =~ /^-.*H/) { # そのまま
print "$host ";
}
if ($option =~ /^-.*r/) {
if ($referer =~ /^($my_site)/) { # 前半36文字
print '/', substr($referer, length($my_site), 36);
} else { # 前半37文字
print substr($referer, 0, 37);
}
} elsif ($option =~ /^-.*R/) { # そのまま
print $referer;
}
print "\n";
}
}
コメント、変数の宣言及びオプション処理を除くと、10行有るか無いかって感じですね。これを実行してみるとこんな感じに...。

7月2日のログを入れてみました
ソートしてグラフを描かないと使い物にならないという声が聞こえてきそうですが、うちのサイトの場合、1日分だとターミナルの1ページに収まってしまい、これで十分という噂も...(^_^;
次回は、1週間分ぐらいのデータで、グラフが描けるようなプログラムを作ってみようと思います。*3
|