回答例 by yama
% /usr/bin/tail log-index crawl-61-247-222-46.naver.jp,Tue Feb 9 03:19:07 JST 2010,Yeti/1.0 (NHN Corp.; http://help.naver.com/robots/), b3091108.crawl.yahoo.net,Tue Feb 9 07:39:48 JST 2010,Mozilla/5.0 (compatible; Yahoo! Slurp/3.0; http://help.yahoo.com/help/us/ysearch/slurp), b3091108.crawl.yahoo.net,Tue Feb 9 15:54:05 JST 2010,Mozilla/5.0 (compatible; Yahoo! Slurp/3.0; http://help.yahoo.com/help/us/ysearch/slurp), 159.226.251.173,Tue Feb 9 17:15:02 JST 2010,Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; InfoPath.1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729),http://stat.sm.u-tokai.ac.jp/~yama/perl/p4.html 159.226.251.173,Tue Feb 9 17:45:18 JST 2010,Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; InfoPath.1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729),http://stat.sm.u-tokai.ac.jp/~yama/perl/p4.html proxy02.nttdata.co.jp,Tue Feb 9 17:53:10 JST 2010,Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; InfoPath.1) Sleipnir/2.5.11,http://stat.sm.u-tokai.ac.jp/~yama/perl/p4.html msnbot-65-55-37-180.search.msn.com,Tue Feb 9 18:17:08 JST 2010,msnbot-media/1.1 (+http://search.msn.com/msnbot.htm), msnbot-65-55-207-69.search.msn.com,Tue Feb 9 22:58:29 JST 2010,msnbot/2.0b (+http://search.msn.com/msnbot.htm), msnbot-65-55-207-69.search.msn.com,Tue Feb 9 22:59:36 JST 2010,msnbot/2.0b (+http://search.msn.com/msnbot.htm), eestec.hu,Wed Feb 10 00:10:19 JST 2010,Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1),このように、あなたのホームページを訪れた人に関する情報が集まりだすと、 いろいろとまとめてみたくなるものです。 (どんな所から来ているかとか、どんなブラウザを使っているかなどの情報がすでに集まってるのですから。) また、ブラウザの種類によって異なった処理を行なうようなことについても考えてみましょう。 ファイルの中から文字列のマッチを行なうような場合には、正規表現が有効になります。 今回は、perlにおける正規表現とパターンマッチ、置換について、またログファイルを扱うのでファイルハンドルについても詳しく扱います。
open(FILEHANDLE,"filename") (または "<filename")
filenameをファイルハンドルFILEHANDLEに結びつけ読み込み用にオープン
open(FILEHANDLE,">filename")
filenameをファイルハンドルFILEHANDLEに結びつけ書き出し用にオープン
open(FILEHANDLE,">>filename")
filenameをファイルハンドルFILEHANDLEに結びつけ追加書き出し用にオープン
いずれの場合も open() は成功すれば真を、失敗すれば偽を返します。
(ファイルがない、パーミッションがないなどの場合が考えられます。)
すでにオープンされているファイルを再びオープンしたり、プログラムの終了時にはファイルは自動的にクローズされるので、close()は必ずしも利用しなければならない訳ではありません。
open(FILE,"filename") || die "I can't open this file\n"
|| 演算子を利用し、open() 演算子が偽の場合に die() が実行されます。die() のメッセージにPerlのプログラム名と行番号を付加したい場合にはメッセージの最後に改行を入れず、付加したくない場合には改行を入れます。
die "I can't open this file" #表示する
die "I can't open this file\n" #表示しない
open(LOG,"log-index");
while ($a=<LOG>){
print $a;
}
これでファイルの内容をずらっと表示することは可能ですが、前回やったように、
これは次のように$_ を利用して簡単に書くことができます。
open(LOG,"log-index");
while (<LOG>){
print;
}
引数を省略した場合には $_ に対して処理が行なわれる性質を利用したものでしたね。
このくらいの処理のためにエディタを利用するのも面倒なので、コマンドラインから実行してみましょう。
% perl -e 'open(LOG,"log-index"); while(<LOG>){ print; }'
150.46.103.154,Mon Jan 20 16:50:01 JST 1997,Mozilla/3.01 (Win95; I)
tcur1ds29.iba.mesh.ad.jp,Mon Jan 20 17:11:02 JST 1997,Mozilla/2.02 [ja] (Win95;I)
(中略)
owari.nmiri.city.nagoya.jp,Mon Jan 20 18:44:11 JST 1997,Mozilla/3.01 (X11; I; SunOS 5.4 sun4m)
210.133.202.2,Mon Jan 20 21:51:04 JST 1997,Mozilla/2.0 (compatible; MSIE 3.01; Windows 95)
どっと出力されてしまいました。
これだけの処理をするなら、cat を使った方が簡単ですね。
では、行番号をつけるなどの付加的な処理を行なってみましょう。
行番号は $. という変数に入っているのでこれを利用します。
open(LOG,"log-index");
while (<LOG>){
print "$. : $_";
}
のように変更しました。
% perl -e 'open(LOG,"log-index"); while(<LOG>){ print "$. : $_"; }'
1 : 150.46.103.154,Fri Nov 22 22:11:48 JST 1996,Mozilla/3.01 (Win95; I)
2 : kyot1du07.kyt.mesh.ad.jp,Sat Nov 23 01:34:08 JST 1996,Mozilla/2.02 [ja] (Win95; I)
(中略)
440 : owari.nmiri.city.nagoya.jp,Mon Jan 20 18:44:11 JST 1997,Mozilla/3.01 (X11; I; SunOS 5.4 sun4m)
441 : 210.133.202.2,Mon Jan 20 21:50:58 JST 1997,Mozilla/2.0 (compatible; MSIE 3.01; Windows 95)
442 : 210.133.202.2,Mon Jan 20 21:51:04 JST 1997,Mozilla/2.0 (compatible; MSIE 3.01; Windows 95)
これらは起動時パラメータを使用してもっと簡単に
% perl -e 'while(<>){ print "$. : $_"; }' log-index
として得ることもできます。
書き込み用、追加書き込み用にオープンしたファイルハンドルは print キーワードと引数のリストの間にファイルハンドルを置きます。
print LOG "date: $date\n";
print STDOUT "Hello, world!\n"; # print "Hello, world!\n"と同じ
$log="log-index"
if (-e $log){
open(LOG,"log-index");
...... #処理
} else {
print "Not exist!";
}
とか
if (-e ".num" && -e ".lock"){
...
}
とか
if (-r "log-index" && -w "log-index"){
...
}
のように利用できます。
ほとんどのファイルテスト演算子は真か偽かを返しますが、
-s 演算子はファイルが空でなければ、バイト単位でファイルの大きさを返すことにより真を返し、-M, -A, -C は日数を返します。
またオペランドとしてファイルハンドルを利用することもできます。
オペランドを省いた時には、いつものように $_ 変数により指定されている名前のファイルをテストします。
| 演算子 | テスト |
|---|---|
| -e | ファイルやディレクトリが存在している |
| -z | ファイルが存在していてかつ大きさが0 |
| -s | ファイルやディレクトリが存在していてかつ大きさが0でない |
| -r | ファイルやディレクトリが読みだし可能 |
| -w | ファイルやディレクトリが書き込み可能 |
| -x | ファイルやディレクトリが実行可能 |
| -o | ファイルやディレクトリをユーザが所有 |
| -R | ファイルやディレクトリが実効ユーザでなく実ユーザにより読みだし可能< |
| -W | ファイルやディレクトリが実効ユーザでなく実ユーザにより書き込み可能 |
| -X | ファイルやディレクトリが実効ユーザでなく実ユーザにより実効可能 |
| -O | ファイルやディレクトリが実効ユーザでなく実ユーザにより所有 |
| -f | 普通のファイルである |
| -d | ディレクトリである |
| -l | シンボリックリンクである |
| -S | ソケットである |
| -p | 名前つきパイプである |
| -b | ブロック特殊デバイスである |
| -c | キャラクタ特殊デバイスである |
| -u | ファイルやディレクトリがsetuidされている |
| -g | ファイルやディレクトリがsetgidされている |
| -k | ファイルやディレクトリがstickyビットがセットされている |
| -t | このファイルハンドルに対して isatty()が真である |
| -T | テキストファイルである |
| -B | バイナリファイルである |
| -M | 最終更新からの日数 |
| -A | 最終アクセスからの日数 |
| -C | inodeの最終変更からの日数 |
open(LOG,"log-index");
while(<LOG>){
if($_=~/Mozilla/){
print "$_";
}
}
とすればよいですが、$_ はここでも省略可能ですから、パターンマッチに使用する変数が$_の場合には単に if(/Mozilla/){ .. で十分です。
ついでにMozilla がある行から、ブラウザ情報を抜き出し、行番号と共に表示し、最後に出現トータルを表示するスクリプトを記述しましょう。
$ARGV[0]="log-index"; #実行時パラメータとして扱われる。
$cnt=0;
while(<>){
if(/Mozilla/){
$cnt++;
@log=split(/,/);
print "$.: $log[2]\n";
}
}
print "Total: $cnt\n"
ここで、$ARGV[0]="log-index"; としているのは、実行時の引数としてファイル名をダイヤモンド演算に渡す方法については、すでに扱いましたが実際にはダイヤモンド演算しは @ARGV を見に行くので、実行時パラメータを利用するスクリプトを書く際に、
実行の確認でいちいち実行時パラメータを入れるのが面倒な時 @ARGV を利用します。
また、コマンドラインからは
. (ドット):改行文字以外の任意の1文字にマッチ
[^0-9] : 数字以外の1文字にマッチ
[^\^] : ^(キャレット)以外の1文字にマッチ
\n 改行文字
\r 復帰文字
\t タブ
\f 改ページ
\b 単語境界
\B 単語境界以外
\nnn 8進値nnnのASCII文字
\xnn 16進値 nnのASCII文字
\cX ASCIIコントロール文字
^ 行頭 (^自身を文字列の先頭で指定する場合には\^で指定)
$ 行末 (パターンの末尾以外ではスカラー変数名として解釈される)
\d (数字) : [0-9]
\w (単語) : [a-zA-Z0-9_]
\s (空白文字) : [ \r\t\n\f]
\D (数字以外) : [^0-9]
\W (単語以外) : [^a-zA-Z0-9_]
\S (空白文字以外) : [^ \r\t\n\f]
abc : aの次がbでその次がcと並んでいるパターン
* : 直前の文字の0回以上の繰り返し
+ : 直前の文字の1回以上の繰り返し
? : 直前の文字の0回または1回の繰り返し
{s,e} : 直前の文字のs回からe回までの繰り返し
a{3,5} aが3個から5個
a{5,} aが5個以上
a{5} aが5個
a{,5} aが5個以下
a|b : a または b
/Blue|Red/ のように並びに利用できる(一文字の選択は/[ab]/を利用)
(パターン) : ()内で指定したパターンにマッチした内容をインデックスを付けて保存
\>整数 (バックスラッシュ+整数n) : n番目の()にマッチした内容
/a(.)b\1/ は axbx にはマッチするがaxby にはマッチしない
/a(.)b(.)c\2d\1/ はaxbycydx などにマッチする
/regexp/i
i オプションは大文字と小文字を同一視します。パターン /sub/ はsub, SUB, Subなどにマッチします。
mdelimiter regexp delimiter
パターンの中でスラッシュを指定する場合には、バックスラッシュを前におきますが、このかわりにスラッシュ以外のデリミタ(区切り記号)を使うことができます。
/\/usr\/local/ とするかわりに m%/usr/local% とした方が見やすいですね。
s/regexp/string/g
g オプションを指定しない場合、最初に見つかったものに対して置換を行ない置換を終了します。
g オプションを指定した場合、マッチ可能な全ての部分について置換を行ないます。
s/regexp/string/i
s/regexp/string/e
$_='ab12cd';
s/\d+/$&*2/e; # $_ は 'ab24cd'に
s/\w/$& x 2/eg; # $_ は 'aabb2244ccdd'に
s#regexp#string#
sのあとにおいた文字がデリミタになります。
tr/abc/ABC/
tr 演算子は元の文字列のリストにある文字を、新しいリストの対応する文字に置き換えます。