「シェル操作課題 (cut, sort, uniq などで集計を行う) 設問編」を解く

シェル操作課題 (cut, sort, uniq などで集計を行う) 設問編 - Yamashiro0217の日記 にインスパイヤされて、できるだけ awk で解いてみました。

問1 このファイルを表示しろ

awk はパターンが真の場合には '{print $0}' を行うので、以下のようにできます。

$ awk '1' sample.txt
server1,1343363124,30,/video.php
server2,1343363110,20,/profile.php
server3,1343363115,7,/login.php
server1,1343363105,8,/profile.php
server2,1343363205,35,/profile.php
server2,1343363110,20,/profile.php
server3,1343363205,30,/login.php
server4,1343363225,12,/video.php
server1,1343363265,7,/video.php

awk では 1 だと他の文字と区別がつきづらいので、海外では 4 とかを使う人が多いみたいです。

問2 このファイルからサーバー名とアクセス先だけ表示しろ

ある項目だけを抜き出すのは awk の得意とするところです。

パターンとして $0 に値を代入することで、強制的に $0 を書き換えると共に、awk では代入することで真になるため、print も省略できます。

$ awk -F',' '$0=$1","$4' sample.txt
server1,/video.php
server2,/profile.php
server3,/login.php
server1,/profile.php
server2,/profile.php
server2,/profile.php
server3,/login.php
server4,/video.php
server1,/video.php

問3 このファイルからserver4の行だけ表示しろ

grep でもいいのですが、grep, sed, awk の正規表現はそれぞれ異なるため、サーバーのログ整理は間違えない (得意な) ツールを使うと良いでしょう。

$ awk '/^server4/' sample.txt
server4,1343363225,12,/video.php

問4 このファイルの行数を表示しろ

awk は END だけだとダメというのを見た記憶があるのですが、nawk, gawk 共に問題ありませんでした。

$ awk 'END {print NR}' sample.txt
9

気になるなら、アクションを 0 にして偽にして何も表示させない方法もあります。

$ awk '0 END {print NR}' sample.txt
9

問5 このファイルをサーバー名、ユーザーIDの昇順で5行だけ表示しろ

この問題は awk で解きません。 awk のソート (asort, asorti) は条件が貧弱なので、sort に任せた方が良いです。

$ sort -t, -k1,1 -k3,3n sample.txt | head -5
server1,1343363265,7,/video.php
server1,1343363105,8,/profile.php
server1,1343363124,30,/video.php
server2,1343363110,20,/profile.php
server2,1343363110,20,/profile.php

問6 このファイルには重複行がある。重複行はまとめて数え行数を表示しろ

古い gawk だと length にバグがあるのでエラーになるかもしれません。 ユニークな数は配列に入れることで簡単に数えられます。

$ awk '{a[$0] = $0} END {print length(a)}' sample.txt
8

問7 このログのUU(ユニークユーザー)数を表示しろ

これも前の設問と同じですね。

$ awk -F',' '{a[$3] = $3} END {print length(a)}' sample.txt
6

問8 このログのアクセス先ごとにアクセス数を数え上位1つを表示しろ

これは sort と head の手を借りることにします。

$ awk -F',' '{++a[$4]} END{for (i in a) {print a[i], i}}' sample.txt | sort -nr | head -1
4 /profile.php

問9 このログのserverという文字列をxxxという文字列に変え、サーバー毎のアクセス数を表示しろ

awk だけなら以下のようにしてできます。

$ awk -F',' '{sub(/^server/, "xxx"); ++a[$1]} END {for(i in a) {print a[i], i}}' sample.txt
3 xxx1
3 xxx2
2 xxx3
1 xxx4

実際には、出力が若干異なりますが、以下のようにパイプで繋げるのが良いと思います。

$ awk -F',' '$0=$1' sample.txt | sort | uniq -c  | sed 's|server|xxx|'
      3 xxx1
      3 xxx2
      2 xxx3
      1 xxx4

問10 このログのユーザーIDが10以上の人のユニークなユーザーIDをユーザーIDでソートして表示しろ

gawk4 なら以下の方法でソートできます。

$ gawk -F',' '$3 >= 10 {a[$3] = $3} END {PROCINFO["sorted_in"] = "@val_num_asc"; for (i in a) {print a[i]}}' sample.txt
12
20
30
35

これもソートは sort に任せるのが良いと思います。

$ awk -F',' '$3 >= 10 \%PAGE_BODY%amp;amp;\%PAGE_BODY%amp;amp; $0 = $3' sample.txt | sort -u
12
20
30
35

ログが数十Gだったら?

awk で良いのではないでしょうか? sort は厳しいのですが、これがゆとりか… | okkyの日記 | スラッシュドット・ジャパン に書かれているように分割して、マージ (それがマージソートなんですけど) すれば、Hadoop なんて使わなくてもできますね。

また、お手軽並列処理 というのを発表したことがありますが、通常の Unix ツールでも案外うまくできそうです。

まとめ

ログ整理が得意と言われる awk ですが、こうして設問を解いてみると、長所と短所が見えてきます。 長所はログの項目の分離や項目の入れ替えといった部分、短所はソートでしょうか。 awk 単独で行う必要がないのであれば、それぞれの得意とするツールを組み合わせて使うのが良さそうです。

tag_nawk.png tag_nawk.png tag_nawk.png tag_nawk.png