awkとシェルでLTSVの取り扱いを簡単にするフィルタを書いてみた
最近、LTSVがお気に入りでApacheのログ始め各種ログをLTSVにしています。
LTSVの何が便利って、普通のCSVやとかと違って、その名の通りラベルが付いてるので順番では無く名前ベースで簡単にアクセスできること。
しかも、順番では無いので、後から項目とかを追加とか入れ替えをしやすくて、集計スクリプトとかと非常に相性が良いことでし。
ただ「日次で集計してグラフ化する」だとか「DBに入れる」とか、そういったシステム化した定常的な解析はRubyなりで書けばいいので問題ないのですが、障害対応やちょっとした思いつきなんかで、awkでワンライナーな分析/集計をしちゃう場合にはLTSVは不便だったりします。
たとえば、下記のようなLTSVベースのログがあるとします。
time:[08/Feb/2013:14:17:53 +0900] req:/ response:100 time:[08/Feb/2013:14:17:54 +0900] req:/test1 response:1 time:[08/Feb/2013:14:17:55 +0900] req:/test2 response:2 time:[08/Feb/2013:14:17:56 +0900] req:/test1 response:10
上記のログのtest1だけの平均レスポンスをさくっと求めたい、とかはよくあるケースですね。
普通にawkで書くとこんな感じ。
% cat access.log| awk -F'\t' '$2 == "req:/test1" {sum += (substr($3,10));cnt++} END{print sum/cnt}'
カラム番号を意識する必要があってぜんぜんLTSV使ってる嬉しさがないです>_<
というか、これならCSVやTSVのほうがまだ楽ですね。substrがいらないので。
この問題を解決するべく、簡単なawkとシェルでltsv.shというフィルタを書いてみました。
これを使うと先ほどのコードは下記のように書けます。
% cat access.log| ltsv.sh 'ltsv("req") == "/test1"{sum += ltsv("response");cnt++};END{print sum/cnt}'
見て分かる通り、awkがそのまま書けるのですが、名前ベースでカラムにアクセスしていることが分かります。
実際の振る舞いとしては、ltsvという関数を追加してLTSVを連想配列に変換しているわけです。
たったこれだけで、めちゃめちゃ可読性があがりました!
もちろん、パイプで繋ぐことも可能です。これまた良くあるn秒以上のリクエストのカウントはこんな感じ。
% cat access.log| ltsv.sh 'int(ltsv("response")) > 2{print $0}'|wc -l
ヤバイ! 我ながら便利だ! ちなみにintでキャストしないと文字列なので正しく数値比較できないです。
こちらの実装は非常にシンプルで、下記のようになります。
This is AWK wrapper to parse LTSV
見たまんま単にltsvという関数を定義して文字列としてくっつけただけですw
Bashのヒアドキュメントで定義できるので、毎回awkにこの関数を手で適用するわけじゃないので、とても簡単です。
ようはinclude的なことをしてるわけですが、awkでどうやれば良いのかわからないので、こんな力技な方法になってしまいました。
とりあえず、動くのでいいですがエレガントじゃないので、
他に良い方法があれば誰か教えて下さい>_<
なんにしても、これで自分的にはltsvで困ることがほぼなくなったので、より利用を促進して行きたいとおもいます。
それではHappy Hacking!
参考URL: