awkを使って特定の日時の範囲でログを抽出する

  • ある日時からある日時までのログを抽出したい(7月31日9時から8月1日9時まで、など)

  • timegrepのような便利なコマンドを追加するという手もあるが、現場ではハードルが高い*1

  • 特別なコマンドを利用することなく、プリインストールされているコマンドで対応するためのメモ

ググると出てくる例

とりあえず、誰かやっていないかググってみると以下のような例が見つかる。

awk -F - '"開始時間" < $1 && $1 <= "終了時間"' /抽出を行うログのPATH
# 時間は抽出を行ログのフォーマットに完全に一致する必要がある(スペース等含む)

この例はsyslogではそれなりにうまく動作するが、仕組みを正しく理解するとどんな場合にも使えるわけではないことが分かる。

ググると出てくる例の仕組みと問題点

ググると出てくる例では以下のような処理をしている。

  1. フィールドセパレータに標準入力(-)を設定して、行の中で一切区切らないように

  2. 一番目のフィールド($1、つまりこの時点では行すべて)の文字列を使って、

  3. 行頭から文字コードの大小を比較して"開始時間"より大きく"終了時間"以下の行を抽出

そのため、上記には以下の4つの問題点がある

  1. そもそも行全部を利用して比較するなら、素直にそうすればよい

  2. syslogのように日時が行頭にあるログでない場合は対応できない

  3. 開始時間と終了時間のspace paddingも意識して指定しないと正しく動作しない

  4. 文字コードの比較なので、syslogの場合は月をまたぐと正しく判定できない*3

上記の問題が解消されないとダメなケースも多いと思われるため、改善できないかやってみる。

改善策

問題点1は、もうちょっとシンプルに、単純に比較対象を$0(行全体)に書き換えれば解決(結果は同じ)

awk '"Sep  0 22:39:08" < $0 && $0 <= "Sep  3 22:40:08"' /var/log/messages

問題点2と問題点3は日時の部分を取得する箇所を指定すれば回避できる

awk '"Sep 3 22:39:08" < sprintf("%s %s %s", $1, $2, $3) && sprintf("%s %s %s", $1, $2, $3) <= "Sep 3 22:40:08"' /var/log/messages 

問題点4は文字コードの問題なので、sedを噛ませて月を数字に置換すれば一応は対応できる*4

syslogを想定した変換用の辞書として以下のファイルを作成して

[root@localhost log]# cat /tmp/month_conv 
s/^Jan/1/g
s/^Feb/2/g
s/^Mar/3/g
s/^Apr/4g/
s/^May/5g/
s/^Jun/6/g
s/^Jul/7/g
s/^Aug/8/g
s/^Sep/9/g
s/^Oct/10/g
s/^Nov/11/g
s/^Dec/12/g

次のような感じで置換した結果をawkで処理すればよい

sed -f /tmp/month_conv /var/log/messages | awk '"7 31 00:36:08" < sprintf("%s %s %s", $1, $2, $3) && sprintf("%s %s %s", $1, $2, $3) <= "9 3 22:39:08"'

正しくしようとすればするだけ手間がかかるので、抽出を行うログのフォーマットと範囲に合わせてリーズナブルな方法で処理すればよさそうだ*5

*1:少なくともSIerでは

*2:これはそもそも"$1はログファイルの日時が記述されている部分"と書いてあったりして怪しいのだけど

*3:文字列比較だとApril>Marchですよね

*4:ちょっとイマイチではある

*5:例えばsyslogで同じ月の中で抽出するなら問題点1を解消したもので十分