読者です 読者をやめる 読者になる 読者になる

awkとシェルでLTSVの取り扱いを簡単にするフィルタを書いてみた

operation シェル linux

最近、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: