シェルで回答してみる「シェル操作課題 (cut, sort, uniq などで集計を行う) 設問編」
毛利です。
最近「とりあえず出来ればいいか」ぐらいでレベルが止まってると感じるので何か工夫(短くする、コマンドの回数を減らす、高速なコマンドを使う)できないかを考えながら解いてみる。
シェル操作課題 (cut, sort, uniq などで集計を行う) 設問編 - Yamashiro0217の日記
データファイルはdata.csvとしてます。OSはMountain Lion。シェルはbash。
$ uname -a Darwin iMac.local 12.2.0 Darwin Kernel Version 12.2.0: Sat Aug 25 00:48:52 PDT 2012; root:xnu-2050.18.24~1/RELEASE_X86_64 x86_64 $ echo $SHELL /bin/bash $ cat <<EOF > data.csv 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 EOF
問2 このファイルからサーバー名とアクセス先だけ表示しろ
cut -d, -f1,4 data.csv
ちなみに -f オプションは以下の様な使い方も出来る。
# 1~3列目 $ cut -d, -f1-3 data.csv | head -1 server1,1343363124,30 # 3列目以降 $ cut -d, -f3- data.csv | head -1 30,/video.php
思いつく限り他のコマンドで(ある程度実用的と思える範囲で)
$ awk -F, '{printf("%s,%s\n",$1,$4)}' data.csv $ sed -e 's/^\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\)$/\1,\4/' data.csv $ perl -F, -a -nle 'print join(",",$F[0],$F[3])' data.csv
問3 このファイルからserver4の行だけ表示しろ
$ egrep "^server4," data.csv
思いつく限り他のコマンドで(ある程度実用的と思える範囲で)
$ awk -F, '/^server4,/{print}' data.csv $ awk -F, '{if("server4"==$1){print}}' data.csv $ sed -ne '/^server4,/p' data.csv $ perl -ne 'print if (/^server4,/)' data.csv
問5 このファイルをサーバー名、ユーザーIDの昇順で5行だけ表示しろ
「サーバー名,unixtime,ユーザーID,アクセス先」とのことなので1,3列目。文字列としてではなく数字としてソート。
$ sort -t, -k1,1 -k3,3n data.csv | 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
- NG例
ハマッてしまった。-kではコンマ区切りでフィールド複数指定すると思ってた。
$ sort -t, -k1,3n data.csv | head -5 server1,1343363105,8,/profile.php server1,1343363124,30,/video.php server1,1343363265,7,/video.php server2,1343363110,20,/profile.php server2,1343363110,20,/profile.php
※ユーザID列でソートされない
問6 このファイルには重複行がある。重複行はまとめて数え行数を表示しろ
$ sort data.csv | uniq | wc -l 8
sortやuniqのオプションで短く出来ないかと思って調べた
$ sort -u data.csv | wc -l 8
こんなもん?
-u
デフォルトの動作と -m オプションの動作では、等しいとされた行のうちの最初のものだけを表示する。
perlだとこんな感じ?
$ perl -nle '$lc++ if(! $d{$_}); $d{$_}=1; END{print $lc}' data.csv 8
こうか。
$ perl -nle '$d{$_}++; END{print scalar keys %d}' data.csv 8
問7 このログのUU(ユニークユーザー)数を表示しろ
$ cut -d, -f3 data.csv | sort | uniq | wc -l 6
さっき覚えたsort -uを使う
$ cut -d, -f3 data.csv | sort -u | wc -l 6
perlだとこんな感じ?
$ perl -F, -a -nle '$d{$F[2]}++; END{print scalar keys %d}' data.csv 6
問8 このログのアクセス先ごとにアクセス数を数え上位1つを表示しろ
$ cut -d, -f4 data.csv | sort | uniq -c | sort -k1n | tail -1 4 /profile.php
問9 このログのserverという文字列をxxxという文字列に変え、サーバー毎のアクセス数を表示しろ
"このログの"とあるので、とりあえず全部置換して集計。
$ sed -e 's/server/xxx/g; s/,.*$//' data.csv | sort | uniq -c 3 xxx1 3 xxx2 2 xxx3 1 xxx4
思いつく限り他のコマンドで(ある程度実用的と思える範囲で)
$ cut -d, -f1 data.csv | sed -e 's/server/xxx/' | sort | uniq -c $ cut -d, -f1 data.csv | sort | uniq -c | sed -e 's/server/xxx/' $ perl -nle 'BEGIN{%d=()}s/server/xxx/g; @F=split /,/; $d{$F[0]}++; END{print join(" ", $d{$_}, $_) for keys %d}' data.csv 1 xxx4 3 xxx1 2 xxx3 3 xxx2
最後のperlはソートしてない。設問にソートは入ってないからいいかなと。
問10 このログのユーザーIDが10以上の人のユニークなユーザーIDをユーザーIDでソートして表示しろ
$ cut -d, -f3 data.csv | egrep '\d\d+' | sort -u
※"01"というIDがあったら表示されてしまうが。
別解
$ awk -F, '{if($3 > 10){print $3}}' data.csv | sort -u