awkでワンライナー( ログ集計 )
ちょっとしたログを集計したりする場合に、いちいちスクリプト書いて実行するのはめんどうですよね、そこでawkの出番です。
シェルコマンドと組み合わせて割と簡単に集計ができます。
いつも書き方忘れていちいち調べてるのでよく使うものを残しておきます。
基本構文
awkではセパレータで区切った文字列を$1,$2...のように持っています
デフォルトのセパレータはスペースです。
サンプルではcatしてパイプで渡してますが、tailとかで垂れ流しとかもできます
- すべてのフィールドを出力する
cat sample.txt | awk '{print $0}'
- 特定のフィールドを出力する
$ cat sample.txt hoge fuga piyo aaaa bbbb cccc dddd eeee ffff $ cat sample.txt | awk '{print $1,$2}' hoge fuga aaaa bbbb dddd eeee
- 行数を出力する
$ cat sample.txt | awk '{print FNR,$1,$2}' 1 hoge fuga 2 aaaa bbbb 3 dddd eeee
組み込み変数「FNR」を使うことで行数を出力することができます
- フィールド数を出力する
$ cat sample.txt hoge fuga piyo aaaa bbbb cccc dddd eeee ffff $ cat sample.txt | awk '{print $(NF-1)}' fuga bbbb eeee
組み込み変数「NF」を使うことでフィールド数を出力することができます
また、組み込み変数から計算した値のフィールドの値を出力することもできます
分岐
- ステータスコードが200以外のものを抽出したいとき
$ cat sample.log Dec 8 19:18:44 hostname 182.250.253.9 domain "GET /hoge HTTP/1.1" 200 - 17370 Dec 8 19:18:44 hostname 153.120.34.51 domain "GET /fuga HTTP/1.0" 404 1379 57864 Dec 8 19:18:45 hostname 49.98.50.118 domain "GET /images/poyo.png HTTP/1.1" 200 243 173 Dec 8 19:18:46 hostname 49.98.50.118 domain "GET /images/piyo.png HTTP/1.1" 200 243 173 Dec 8 19:18:46 hostname 153.120.34.51 domain "GET /fuga HTTP/1.0" 404 1379 57864 Dec 8 19:18:46 hostname 122.29.23.11 domain "GET /images/moge.png HTTP/1.1" 200 21595 669 Dec 8 19:18:47 hostname 106.158.127.211 domain "GET /hoge HTTP/1.1" 200 - 14123 Dec 8 19:18:47 hostname 49.98.50.118 domain "GET /images/poyo.png HTTP/1.1" 200 243 173 Dec 8 19:18:48 hostname 49.98.50.118 domain "GET /images/piyo.png HTTP/1.1" 200 243 173 $ cat sample.log | awk '{if($10!=200)print $0}' Dec 8 19:18:44 hostname 153.120.34.51 domain "GET /fuga HTTP/1.0" 404 1379 57864 Dec 8 19:18:46 hostname 153.120.34.51 domain "GET /fuga HTTP/1.0" 404 1379 57864
- レスポンスのバイト数が○○以上のアクセスを絞りたいとき
$ cat sample.log | awk '{if($11>1000)print $0}' Dec 8 19:18:44 hostname 153.120.34.51 domain "GET /fuga HTTP/1.0" 404 1379 57864 Dec 8 19:18:46 hostname 153.120.34.51 domain "GET /fuga HTTP/1.0" 404 1379 57864 Dec 8 19:18:46 hostname 122.29.23.11 domain "GET /images/moge.png HTTP/1.1" 200 21595 669
- リクエストパスに○○が含まれているものを絞りたいとき
$ cat sample.log | awk '{if($8~/yo/)print $0}' Dec 8 19:18:45 hostname 49.98.50.118 domain "GET /images/poyo.png HTTP/1.1" 200 243 173 Dec 8 19:18:46 hostname 49.98.50.118 domain "GET /images/piyo.png HTTP/1.1" 200 243 173 Dec 8 19:18:47 hostname 49.98.50.118 domain "GET /images/poyo.png HTTP/1.1" 200 243 173 Dec 8 19:18:48 hostname 49.98.50.118 domain "GET /images/piyo.png HTTP/1.1" 200 243 173
こちらは正規表現が使えます
- 複数条件
$ cat sample.log| awk '{if($3~/19:18:46/ && $10!=200 )print $0}' Dec 8 19:18:46 hostname 153.120.34.51 domain "GET /fuga HTTP/1.0" 404 1379 57864
セパレータ
- 明示的にセパレータを指定
$ cat sample.csv aaa,bbb,ccc,ddd eee,fff,ggg,hhh iii,jjj,kkk,lll $ cat sample.csv | awk -F"," '{print $1,$2}' aaa bbb eee fff iii jjj
-Fオプションでセパレータを指定できます。 デフォルトはスペースですが、別のものを指定したい場合に使用します。これでCSVとかもいけますね
- セパレータを複数指定する
$ cat sample.log Dec 8 19:18:44 hostname 182.250.253.9 domain "GET /hoge HTTP/1.1" 200 - 17370 Dec 8 19:18:44 hostname 153.120.34.51 domain "GET /fuga HTTP/1.0" 404 1379 57864 Dec 8 19:18:45 hostname 49.98.50.118 domain "GET /images/poyo.png HTTP/1.1" 200 243 173 Dec 8 19:18:46 hostname 49.98.50.118 domain "GET /images/piyo.png HTTP/1.1" 200 243 173 Dec 8 19:18:46 hostname 153.120.34.51 domain "GET /fuga HTTP/1.0" 404 1379 57864 Dec 8 19:18:46 hostname 122.29.23.11 domain "GET /images/moge.png HTTP/1.1" 200 21595 669 Dec 8 19:18:47 hostname 106.158.127.211 domain "GET /hoge HTTP/1.1" 200 - 14123 Dec 8 19:18:47 hostname 49.98.50.118 domain "GET /images/poyo.png HTTP/1.1" 200 243 173 Dec 8 19:18:48 hostname 49.98.50.118 domain "GET /images/piyo.png HTTP/1.1" 200 243 173 $ cat sample.log | awk -F"[: ]" '{print $4,$5,$10}' 18 44 /hoge 18 44 /fuga 18 45 /images/poyo.png 18 46 /images/piyo.png 18 46 /fuga 18 46 /images/moge.png 18 47 /hoge 18 47 /images/poyo.png 18 48 /images/piyo.png
ログはスペースで区切って.....時間はコロンで区切って......なんて時にセパレータを複数指定することができます
セパレータにしたい文字列を[]で複数指定します
- 出力時の区切り文字を指定する
$ cat sample.log | awk -v OFS=":" '{print $1,$2}' /hoge:200 /fuga:404 /images/poyo.png:200 /images/piyo.png:200 /fuga:404 /images/moge.png:200 /hoge:200 /images/poyo.png:200 /images/piyo.png:200
$1と$2の間にコロンが出力されるようになります
集計
- 平均
$ cat sample.log | awk -v OFS="\n" '{sum+=$12}END{print "sum: "sum,"avg: "sum/NR}' sum: 148582 avg: 16509.1
よくある平均出したいみたいな時。
ENDブロックはBEGINブロックとセットで書くことが多いですが、とくに必要なければ省略も可能です。
また、変数の初期化もとくに値をセットする必要がなければ必要ありません。
上記コマンドは下記コマンドと等価
$ cat sample.log | awk -v OFS="\n" 'BEGIN{sum=0}{sum+=$12}END{print "sum: "sum,"avg: "sum/NR}'
* 流れ
* BEGIN処理
* 行ごとに処理
* END処理
- グループごとの行数
$ cat sample.log | awk -v OFS=":" -F"[: ]" '{print $3,$4,$5}' | sort | uniq -c 2 19:18:44 1 19:18:45 3 19:18:46 2 19:18:47 1 19:18:48 # awkだけでやった場合はこんな感じ $ cat sample.txt | awk -F"[: ]" '{name[$3":"$4":"$5]++;}END{for(t in name){print name[t],t}}' 2 19:18:44 1 19:18:45 3 19:18:46 2 19:18:47 1 19:18:48
時間ごとのアクセス数を出してみました
連想配列が使えるので普段スクリプト書いてやっているようなこともワンライナーでできたりします
まぁ、シェルコマンドと合わせても実現できますね
実践
# レスポンスタイム毎の件数の算出 $ grep --- access_log | awk '{ c[int($18/1000000)]++;} END{for(t in c){print t"s",c[t]}}' | sort -n 0s 55113 1s 237 2s 101 3s 138 4s 146 5s 209 6s 297 7s 309 8s 291 9s 269 10s 235 # 分間、IP毎のエラーログの集計 $ grep -P '10 17:(0|1|2)' error_log.2014-12-10 | awk -F"[: ]" '{c[$3":"$4,$21]++} END{for( i in c ){ split(i,is,SUBSEP); print is[1],is[2],c[is[1],is[2]]}}' | sort 17:16 (10.0.32.202) 36 17:17 (10.0.32.201) 39 17:17 (10.0.32.205) 33 17:18 (10.0.32.202) 57 17:18 (10.0.32.204) 15
awkでは多次元配列も扱うことができます、これを用いることで若干細かな集計も行うことができます。
まぁこれくらいやるならスクリプト書いたほうがいい気もしますけどねw
おまけ
- 色付け
cat sample.log | awk '{print "\033[35m",$1,"\033[37m"}'
シェルスクリプトとかと変わらないですね
とりあえずこのくらい覚えておけばぱっとワンライナーでログが集計できるんじゃないかと思います。