Apache等のアクセスログに生成される、複数ホストで同一日付のファイルをAWSTATSなどで使用する場合があります。
その場合logresolvemergeなどを利用するのですが、logresolvemergeは各ファイルが内部でソートされていることを前提としています。
ログは書きこみプロセスなどの関係で時折内部で日時が前後することがあるため、ファイルの内部をソートし直す必要があります。

スクリプト言語等のループ等を利用してもソートすることができるのですが、今回はシェルを使った方法を紹介したいと思います。

使うコマンドは下記になります。

  • cat
  • sort
  • sed

例として、ログの中身が下記のようになっているファイルが数十個あるとします。

125.54.146.207 – app [21/Nov/2011:15:30:28 +0900] “GET /hoge.js HTTP/1.1” 304 –
125.54.146.207 – app [21/Nov/2011:15:30:28 +0900] “GET /moge.js HTTP/1.1” 304 –
125.54.146.207 – app [21/Nov/2011:15:29:01 +0900] “GET / HTTP/1.1” 304 –
125.54.146.207 – app [21/Nov/2011:15:30:26 +0900] “GET /hoge.php HTTP/1.1” 200 13827
125.54.146.207 – app [21/Nov/2011:15:30:28 +0900] “GET / HTTP/1.1” 304 –

ここでは、hoge1.log, hoge2.logの以下の2ファイルをサンプルとして使用します。

$ cat hoge1.log
125.54.146.207 - app [21/Nov/2011:15:30:28 +0900] "GET /hoge.js HTTP/1.1" 304 -
125.54.146.207 - app [21/Dec/2011:15:30:28 +0900] "GET /moge.js HTTP/1.1" 304 -
125.54.146.207 - app [05/Nov/2011:15:29:01 +0900] "GET / HTTP/1.1" 304 -
125.54.146.207 - app [18/Apr/2011:15:30:26 +0900] "GET /hoge.php HTTP/1.1" 200 13827
125.54.146.207 - app [22/Nov/2001:15:30:28 +0900] "GET / HTTP/1.1" 304 -

$ cat hoge2.log
125.54.146.207 - app [27/May/2011:16:34:01 +0900] "GET /change/hoge.js HTTP/1.1" 304 -
125.54.146.207 - app [21/Nov/2011:15:11:22 +0900] "GET /moge.js HTTP/1.1" 304 -
125.54.146.207 - app [01/Nov/2010:05:29:01 +0900] "GET /moge/ HTTP/1.1" 304 -
125.54.146.207 - app [13/Oct/2011:13:08:26 +0900] "GET /hoge.php HTTP/1.1" 200 13827
125.54.146.207 - app [11/Mar/2011:14:54:28 +0900] "GET / HTTP/1.1" 304 -

○cat

はじめに、これらのファイルを連結します

$ cat *
125.54.146.207 - app [21/Nov/2011:15:30:28 +0900] "GET /hoge.js HTTP/1.1" 304 -
125.54.146.207 - app [21/Dec/2011:15:30:28 +0900] "GET /moge.js HTTP/1.1" 304 -
125.54.146.207 - app [05/Nov/2011:15:29:01 +0900] "GET / HTTP/1.1" 304 -
125.54.146.207 - app [18/Apr/2011:15:30:26 +0900] "GET /hoge.php HTTP/1.1" 200 13827
125.54.146.207 - app [22/Nov/2001:15:30:28 +0900] "GET / HTTP/1.1" 304 -
125.54.146.207 - app [27/May/2011:16:34:01 +0900] "GET /change/hoge.js HTTP/1.1" 304 -
125.54.146.207 - app [21/Nov/2011:15:11:22 +0900] "GET /moge.js HTTP/1.1" 304 -
125.54.146.207 - app [01/Nov/2010:05:29:01 +0900] "GET /moge/ HTTP/1.1" 304 -
125.54.146.207 - app [13/Oct/2011:13:08:26 +0900] "GET /hoge.php HTTP/1.1" 200 13827
125.54.146.207 - app [11/Mar/2011:14:54:28 +0900] "GET / HTTP/1.1" 304 -

連結しただけなのでまだ順番は変わりません。

次に、その出力を普通にソートしてみます。
sortコマンドには以下のようにセパレータで区切られたフィールドと範囲指定でソートキーを優先順に複数定義することができる-kオプションがあります。

以下は、区切り文字を半角スペースとして1行をフィールド分割したときの2番目のフィールドの2文字目から3文字目を第1ソートキー、1番目のフィールドを第2ソートキーとすることを表します。

$ cat test.log
b A03z 2
a A12f 3
a B03d 1

$ sort -t " " -k 2.2,2.3 -k 1,1 test.log
a B03d 1
b A03z 2
a A12f 3

これを利用して以下のログファイルをソートしてみます。

$ cat hoge1.log
125.54.146.207 - app [21/Nov/2011:15:30:28 +0900] "GET /hoge.js HTTP/1.1" 304 -
125.54.146.207 - app [21/Dec/2011:15:30:28 +0900] "GET /moge.js HTTP/1.1" 304 -
125.54.146.207 - app [05/Nov/2011:15:29:01 +0900] "GET / HTTP/1.1" 304 -
125.54.146.207 - app [18/Apr/2011:15:30:26 +0900] "GET /hoge.php HTTP/1.1" 200 13827
125.54.146.207 - app [22/Nov/2001:15:30:28 +0900] "GET / HTTP/1.1" 304 -

$ cat hoge2.log
125.54.146.207 - app [27/May/2011:16:34:01 +0900] "GET /change/hoge.js HTTP/1.1" 304 -
125.54.146.207 - app [21/Nov/2011:15:11:22 +0900] "GET /moge.js HTTP/1.1" 304 -
125.54.146.207 - app [01/Nov/2010:05:29:01 +0900] "GET /moge/ HTTP/1.1" 304 -
125.54.146.207 - app [13/Oct/2011:13:08:26 +0900] "GET /hoge.php HTTP/1.1" 200 13827
125.54.146.207 - app [11/Mar/2011:14:54:28 +0900] "GET / HTTP/1.1" 304 -

$ cat * | sort -t " " -k 4.9,4.12 -k 4.5,4.7 -k 4.2,4.3 -k 4.14,4.15 -k 4.17,4.18 -k 4.20,4.21
125.54.146.207 - app [22/Nov/2001:15:30:28 +0900] "GET / HTTP/1.1" 304 -
125.54.146.207 - app [01/Nov/2010:05:29:01 +0900] "GET /moge/ HTTP/1.1" 304 -
125.54.146.207 - app [18/Apr/2011:15:30:26 +0900] "GET /hoge.php HTTP/1.1" 200 13827
125.54.146.207 - app [21/Dec/2011:15:30:28 +0900] "GET /moge.js HTTP/1.1" 304 -
125.54.146.207 - app [11/Mar/2011:14:54:28 +0900] "GET / HTTP/1.1" 304 -
125.54.146.207 - app [27/May/2011:16:34:01 +0900] "GET /change/hoge.js HTTP/1.1" 304 -
125.54.146.207 - app [05/Nov/2011:15:29:01 +0900] "GET / HTTP/1.1" 304 -
125.54.146.207 - app [21/Nov/2011:15:11:22 +0900] "GET /moge.js HTTP/1.1" 304 -
125.54.146.207 - app [21/Nov/2011:15:30:28 +0900] "GET /hoge.js HTTP/1.1" 304 -
125.54.146.207 - app [13/Oct/2011:13:08:26 +0900] "GET /hoge.php HTTP/1.1" 200 13827

期待通りのソートを行うことができませんでした。
これはログを出力したマシンのロケールの問題で、月のフィールドが3文字コードになっているため、文字列では大小を比較することができませんでした。

このような場合のために、sortには-Mオプションが用意されており、

Jan $ cat * | sed -e "s:/Jan/:/01/:" -e "s:/Feb/:/02/:" -e "s:/Mar/:/03/:" -e "s:/Apr/:/04/:" -e "s:/May/:/05/:" -e "s:/Jun/:/06/:" -e "s:/Jul/:/07/:" -e "s:/Aug/:/08/:" -e "s:/Sep/:/09/:" -e "s:/Oct/:/10/:" -e "s:/Nov/:/11/:" -e "s:/Dec/:/12/:"

125.54.146.207 - app [21/11/2011:15:30:28 +0900] "GET /hoge.js HTTP/1.1" 304 -
125.54.146.207 - app [21/12/2011:15:30:28 +0900] "GET /moge.js HTTP/1.1" 304 -
125.54.146.207 - app [05/11/2011:15:29:01 +0900] "GET / HTTP/1.1" 304 -
125.54.146.207 - app [18/04/2011:15:30:26 +0900] "GET /hoge.php HTTP/1.1" 200 13827
125.54.146.207 - app [22/11/2001:15:30:28 +0900] "GET / HTTP/1.1" 304 -
125.54.146.207 - app [27/05/2011:16:34:01 +0900] "GET /change/hoge.js HTTP/1.1" 304 -
125.54.146.207 - app [21/11/2011:15:11:22 +0900] "GET /moge.js HTTP/1.1" 304 -
125.54.146.207 - app [01/11/2010:05:29:01 +0900] "GET /moge/ HTTP/1.1" 304 -
125.54.146.207 - app [13/10/2011:13:08:26 +0900] "GET /hoge.php HTTP/1.1" 200 13827
125.54.146.207 - app [11/03/2011:14:54:28 +0900] "GET / HTTP/1.1" 304 -

sedでは上記のように-eオプションを連結することで、個々の置換条件を複数適用することができます。
また、パターンの最初のsの直後をセパレータとみなすので、/以外でもセパレータとして使用することができ、パターン内に/がある場合などに便利です。

○sort

それでは、この結果をソートにかけるようにパイプで連結します。

$ cat * | sed -e "s:/Jan/:/01/:" -e "s:/Feb/:/02/:" -e "s:/Mar/:/03/:" -e "s:/Apr/:/04/:" -e "s:/May/:/05/:" -e "s:/Jun/:/06/:" -e "s:/Jul/:/07/:" -e "s:/Aug/:/08/:" -e "s:/Sep/:/09/:" -e "s:/Oct/:/10/:" -e "s:/Nov/:/11/:" -e "s:/Dec/:/12/:" | sort -t " " -k 4.8,4.11 -k 4.5,4.6 -k 4.2,4.3 -k 4.13,4.14 -k 4.16,4.17 -k 4.19,4.20
125.54.146.207 - app [22/11/2001:15:30:28 +0900] "GET / HTTP/1.1" 304 -
125.54.146.207 - app [01/11/2010:05:29:01 +0900] "GET /moge/ HTTP/1.1" 304 -
125.54.146.207 - app [11/03/2011:14:54:28 +0900] "GET / HTTP/1.1" 304 -
125.54.146.207 - app [18/04/2011:15:30:26 +0900] "GET /hoge.php HTTP/1.1" 200 13827
125.54.146.207 - app [27/05/2011:16:34:01 +0900] "GET /change/hoge.js HTTP/1.1" 304 -
125.54.146.207 - app [13/10/2011:13:08:26 +0900] "GET /hoge.php HTTP/1.1" 200 13827
125.54.146.207 - app [05/11/2011:15:29:01 +0900] "GET / HTTP/1.1" 304 -
125.54.146.207 - app [21/11/2011:15:11:22 +0900] "GET /moge.js HTTP/1.1" 304 -
125.54.146.207 - app [21/11/2011:15:30:28 +0900] "GET /hoge.js HTTP/1.1" 304 -
125.54.146.207 - app [21/12/2011:15:30:28 +0900] "GET /moge.js HTTP/1.1" 304 -

期待通りにソートを行うことができました。

○sed

上記のままでもいいのですが、月がソート用に数字変換されたままですので、最後に元に戻します。

$ cat * | sed -e "s:/Jan/:/01/:" -e "s:/Feb/:/02/:" -e "s:/Mar/:/03/:" -e "s:/Apr/:/04/:" -e "s:/May/:/05/:" -e "s:/Jun/:/06/:" -e "s:/Jul/:/07/:" -e "s:/Aug/:/08/:" -e "s:/Sep/:/09/:" -e "s:/Oct/:/10/:" -e "s:/Nov/:/11/:" -e "s:/Dec/:/12/:" | sort -t " " -k 4.8,4.11 -k 4.5,4.6 -k 4.2,4.3 -k 4.13,4.14 -k 4.16,4.17 -k 4.19,4.20 | sed -e "s:/01/:/Jan/:" -e "s:/02/:/Feb/:" -e "s:/03/:/Mar/:" -e "s:/04/:/Apr/:" -e "s:/05/:/Apr/:" -e "s:/06/:/Jun/:" -e "s:/07/:/Jul/:" -e "s:/08/:/Aug/:" -e "s:/09/:/Sep/:" -e "s:/10/:/Oct/:" -e "s:/11/:/Nov/:" -e "s:/12/:/Dec/:"
125.54.146.207 - app [22/Nov/2001:15:30:28 +0900] "GET / HTTP/1.1" 304 -
125.54.146.207 - app [01/Nov/2010:05:29:01 +0900] "GET /moge/ HTTP/1.1" 304 -
125.54.146.207 - app [11/Mar/2011:14:54:28 +0900] "GET / HTTP/1.1" 304 -
125.54.146.207 - app [18/Apr/2011:15:30:26 +0900] "GET /hoge.php HTTP/1.1" 200 13827
125.54.146.207 - app [27/Apr/2011:16:34:01 +0900] "GET /change/hoge.js HTTP/1.1" 304 -
125.54.146.207 - app [13/Oct/2011:13:08:26 +0900] "GET /hoge.php HTTP/1.1" 200 13827
125.54.146.207 - app [05/Nov/2011:15:29:01 +0900] "GET / HTTP/1.1" 304 -
125.54.146.207 - app [21/Nov/2011:15:11:22 +0900] "GET /moge.js HTTP/1.1" 304 -
125.54.146.207 - app [21/Nov/2011:15:30:28 +0900] "GET /hoge.js HTTP/1.1" 304 -
125.54.146.207 - app [21/Dec/2011:15:30:28 +0900] "GET /moge.js HTTP/1.1" 304 -

こちらの記事はなかの人(memorycraft)監修のもと掲載しています。
元記事は、こちら