シェル操作課題 (cut, sort, uniq などで集計を行う) 設問編 - Yamashiro0217の日記
何となく暇だったので。
UNIXのテキスト操作系コマンドは非常に便利なので使いこなせると捗る。
問題の性質上、凝った解き方やログファイルの内容に依存した解き方ではなくスタンダードなやり方の方がよいと思う。
問1 このファイルを表示しろ
$ cat log.out 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
直接見たいときは普通 more/less/vi あたりを使うけど。
問2 このファイルからサーバー名とアクセス先だけ表示しろ
$ awk -F, '{print $1","$4}' log.out 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
想定期待結果にカンマが使われてるので表示部分に "," が入ってる。
表示する要素が増えてくると、いちいちセパレーターを間に挟むのがめんどくさいので、出力用セパレーターOFSを指定してもよいかも。
$ awk 'BEGIN{FS=OFS=","} {print $1,$4}' log.out
問3 このファイルからserver4の行だけ表示しろ
$ awk -F, '$1=="server4"' log.out server4,1343363225,12,/video.php
場合によってはgrepでもありかな。
$ grep '^server4,' log.out
問4 このファイルの行数を表示しろ
$ wc -l < log.out 9
別に標準入力じゃなくてもいいんだけど、ファイルを指定するとファイル名が出るので。
$ wc -l log.out 9 log.out
問5 このファイルをサーバー名、ユーザーIDの昇順で5行だけ表示しろ
$ sort -t, -k1,1 -k3,3n log.out | 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
sortコマンドは awk や perl と言ったスクリプト言語ではちょっと代替できない便利さがありますね。あまり使わないけど paste コマンドあたりも似たような代替不能性が。
問6 このファイルには重複行がある。重複行はまとめて数え行数を表示しろ
$ sort -u log.out | wc -l 8
"sort | uniq" でもよい。
問7 このログのUU(ユニークユーザー)数を表示しろ
$ awk -F, '{print $3}' log.out | sort -u | wc -l 6
問8 このログのアクセス先ごとにアクセス数を数え上位1つを表示しろ
$ awk -F, '{print $4}' log.out | sort | uniq -c | sort -nr | head -1 4 /profile.php
"sort | uniq -c | sort -n" はマジ便利なので覚えましょう。チョチョイと統計取りました的結果が出せるのでよい。
問9 このログのserverという文字列をxxxという文字列に変え、サーバー毎のアクセス数を表示しろ
$ sed -e 's/^server/xx/; s/,.*//' log.out | sort | uniq -c 3 xx1 3 xx2 2 xx3 1 xx4
これは手抜きである。ちゃんとやるなら
$ awk -F, '{sub("^server", "xx", $1); print $1}' log.out
あるいは
$ awk -F, '{print $1}' log.out | sed -e 's/server/xx/'
と分けるとawkの関数とか覚えなくてもよいので楽。
問10 このログのユーザーIDが10以上の人のユニークなユーザーIDをユーザーIDでソートして表示しろ
$ awk -F, '$3>=10 {print $3}' log.out | sort -nu 12 20 30 35
その他
大抵のことは grep/awk/sed/sort/uniq 辺りを駆使すればできるし、スクリプト言語のワンライナーを使うよりもタイプ数が短くなって簡単。覚えるの大変と言っても、よく使うオプションだけ覚えておけばよいし、凝ったことがしたいならperlやrubyでスクリプトを書いたほうが楽。
長めのテキスト処理をしたいときや、ちょっと凝った関数を使いたいときはスクリプト言語の力を借りると便利。
例えば今回の例で言えば、「UNIX epochを日付に直したい」とか、「ユーザーIDをユーザー名にしたい」とか。
$ perl -F, -anle '$t=localtime $F[1]; $n=getpwuid $F[2]; print "$t : $n" if $F[3] eq "/login.php"' log.out Fri Jul 27 12:25:15 2012 : user7 Fri Jul 27 12:26:45 2012 : user30
login.phpにアクセスした日付とユーザー名を知りたい、みたいな?
あとは、例えばこういうファイルがあったとして、どの関数から何バイトのメモリー割り当てがされたか把握する場合とか。
$ cat log2.out 128 bytes of heap allocated from func001 665536 bytes of heap allocated from func002 1024 bytes of heap allocated from func003 32 bytes of heap allocated from func002 256 bytes of heap allocated from func003 256 bytes of heap allocated from func004 512 bytes of heap allocated from func001 256 bytes of heap allocated from func001 1024 bytes of heap allocated from func003 256 bytes of heap allocated from func002
$ perl -nle 'if(/(\d+) bytes of heap allocated from (.+)/){ $h{$2}+=$1 }; END{ print "$_ $h{$_}" foreach keys %h}' log2.out | sort -nrk2 func002 665824 func003 2304 func001 896 func004 256
あとはクソ長いログのどこかに変化点があるようなとき、ログをざっくり眺めたい場合とか。
$ perl -nle 'print "$. $_" unless $.%10000' verylongfile.txt
10000行ごとに行番号つきでprintする。awkでもNRを使って似たようなことができそうだけど、詳しくないので。
あとは grep -A/-B 辺りも非常に便利なので覚えておいて損はない。残念ながらこのオプションはGNU拡張なのですが、非GNU環境ではportしてでも使うことをオススメします。