第23回シェル芸勉強会へ遠隔参加
イマイチ雨の降りが悪い梅雨の最中ですが、会場の様子を後ろから写真に撮るのがいつ実現出来るのかもう分からなくなってきた今日この頃です。シェル芸勉強会へ遠隔参加する福岡サテライト会場も、今回で7回目を数えることになりました。会場のご提供いただいているベータソフト様と管理ご担当者様、ありがとうございます。今回は午前中が別件の用事があったため、午後の部から参加することになりました。
問題出題者の上田さん、ありがとうございます。参加者の皆様お疲れ様でした。福岡サテライトの参加者はのべ9人でした。(途中出入有)初めての参加者は1名でした。
勉強会の情報
勉強会主催者の上田さんが公開されているページをご覧ください。 b.ueda.tech
開始前
福岡サテライト会場にも少しずつ顔なじみな方が増えてきたように思います。 ということで、僭越ながらSoftware Design 6月号のbash特集記事と、シェルスクリプトマガジンvol.38を宣伝させていただきました。
勉強会ライブ配信前のイントロで話したスライドはこちらです。時間が足りず7ページまでしか話せず。
開始
オープンデータについては・・・残念ながらPDFとか画像とかXLSですよねー。久留米市で人口データが公開されていますが、HTMLのテーブルになっている分については、シェルスクリプトでスクレイピングしてCSVにする処理を以前ブログに書いたのでここで紹介しておきます。
今回の問題は比較的正統派な問題が多かったように思います。初心者の方が多い福岡サテライト会場にはちょうどいい感じだったかもしれません。
福岡サテライト会場で解説した内容を記載します。完全に解けることよりも考え方や余談を重視するスタイルです。
解答例はmacOSで作成しました。GNU版のコマンドはguniq
など頭にgが付くコマンドになっている場合があります。
Q1
年月をキーにしてデータを縦に並べる処理。
とりあえずはawk一発芸で作成しました。出題者上田さんの解答のように、分割して処理した方が分かりやすいかもしれません。
福岡ではawkに慣れていない人が多かったので、-F
オプション、三項演算子、sprintf
、i
と$i
の違いなどについて解説しました。
$ awk -F, 'NR>1{for(i=2;i<=13;i++){m=sprintf("%02d",i-1);print $1m,$i!=""?$i:"0"}}' landing.csv > monthly_typhoon
勉強会の時には話しませんでしたが、macOSやFreeBSDなどのBSD系OSに付属のodコマンドはマルチバイトなテキストデータのダンプに対応しているので、BOM付きデータの確認が分かりやすいです。下記のコマンドで、最初の3バイトが通常のテキストデータではないことがわかります。
$ curl -s http://www.data.jma.go.jp/fcd/yoho/typhoon/statistics/landing/landing.csv | od -t x1c | head -n 2 0000000 ef bb bf e5 b9 b4 2c 31 e6 9c 88 2c 32 e6 9c 88 357 273 277 年 ** ** , 1 月 ** ** , 2 月 ** **
Q2
年毎台風上陸数の基本的な集計。
福岡サテライトでは、基本的な集計方法について解説しました。 ポイントは6桁の年月から月を削って4桁の年にし、年をキーにして合計する点です。
具体的な処理方法としては、awkのsubstr()
関数と連想配列使う、もしくはTukubaiのself
とsm2
を使うなどです。
$ cat monthly_typhoon | awk '{print substr($1,1,4),$2}' | awk '{a[$1]+=$2}END{for(v in a){print v,a[v]}}' $ awk '{a[substr($1,1,4)]+=$2}END{for(v in a){print v,a[v]}}' monthly_typhoon $ cat monthly_typhoon | self 1.1.4 2 | sm2 1 1 2 2
勉強会終了後に、出題者上田さんの解答を参考に作った解答がこちらです。実行結果何も出力されないので整合性が取れているのが確認出来ます。
$ paste <(cat monthly_typhoon | self 1.1.4 2 | sm2 1 1 2 2) <(tail -n +2 landing.csv | awk -F, '{print $1,$NF==""?0:$NF}') | awk '$1!=$3 || $2!=$4'
Q3
各月で台風が上陸する確率。
これは勘違いをしていました。(上陸した台風の数を合計した数を月の数で割っていた。台風の数ではなく台風が上陸した月の数を数えなくてはいけない。) 勉強会終了後、下記の解答を考えたので載せておきます。
まずは年月の月だけを切り出します。上陸数については1以上は全て1に変換しました。(0と1で上陸したかしていないかを表現する)
$ cat monthly_typhoon | awk '{print substr($1,5),$2>=1?1:0}' 01 0 02 0 03 0 04 0 05 0 06 0 07 1 08 0 09 0 10 1 ....
次に並べ替えてTukubaiのcountで各行の行数を集計します。2カラム目が0の行が上陸しなかった場合で、1が上陸した場合です。3カラム目がそれぞれの回数になります。
$ cat monthly_typhoon | awk '{print substr($1,5),$2>=1?1:0}' | sort | count 1 2 01 0 65 02 0 65 03 0 65 04 0 64 04 1 1 05 0 63 05 1 2 06 0 56 06 1 9 07 0 39 ....
これをTukubaiのmap
コマンドで2次元展開します。1カラム目が月、2カラム目が上陸しなかった数、3カラム目が上陸した数になります。1行めのヘッダは今後不要になります。
$ cat monthly_typhoon | awk '{print substr($1,5),$2>=1?1:0}' | sort | count 1 2 | map num=1 * 0 1 01 65 0 02 65 0 03 65 0 04 64 1 05 63 2 06 56 9 07 39 26 08 24 41 09 24 41 10 52 13 11 64 1 12 65 0
あとはsed
で1行目のヘッダを消し、awk
で確率を計算すれば完成です。Tukubaiのmapコマンド便利という解答例でした。
$ cat monthly_typhoon | awk '{print substr($1,5),$2>=1?1:0}' | sort | count 1 2 | map num=1 | sed 1d | awk '{print $1,$3/($2+$3)}' 01 0 02 0 03 0 04 0.0153846 05 0.0307692 06 0.138462 07 0.4 08 0.630769 09 0.630769 10 0.2 11 0.0153846 12 0
Q4
各年で台風が最初に上陸した月を抽出して、月毎に回数をカウントする。
出題者上田さんの解答とほぼ同じでした。
GNU版のuniq
を使った場合(macOSではguniq
)と、Tukubaiコマンドのgetfirst
を使った場合の解答例を示します。
$ cat monthly_typhoon | grep -v ' 0$' | awk '{print substr($1,1,4),substr($1,5)}' | guniq -w 5 | awk '{print $2}' | sort | uniq -c 1 04 2 05 9 06 21 07 19 08 7 09 2 10 $ cat monthly_typhoon | grep -v ' 0$' | self 1.1.4 1.5 | getfirst 1 1 | self 2 | sort | uniq -c 1 04 2 05 9 06 21 07 19 08 7 09 2 10
Q5
台風が上陸しなかった年を抽出。Tukubaiのコマンド使ってるだけで、これも出題者上田さんの解答例とほぼ同じでした。
$ cat monthly_typhoon | grep ' 0$' | self 1.1.4 | count 1 1 | awk '$2==12{print $1}' 1984 1986 2000 2008
しかし福岡サテライト参加者から、天才的な解答が出てきました。「使うファイルって指定されてないですよね????」
$ cat landing.csv | grep ',,,,,,,,,,,,,' | sed 's/,,,,,,,,,,,,,//' ファイルの指定がなかったので笑 Q5 #シェル芸
— wataru kashii (@kecy_) 2016年6月18日
あああ元のCSVファイルでコンマ,
が13個連続して並んでれば、台風上陸していませんね!!
Q6
これはデータ確認のための問題でした。
Q7
これもほぼ会長と同じ解答。福岡ではjoin
ではなくpaste
コマンドで横に並べてから処理する方法を話したが、あくまでもこれは全ての地区でひったくりが起きているという前提じゃないとダメ!。普通はちゃんとjoin
しようと注意喚起しておいた。
$ paste -d' ' <(cat hittakuri | sort -s -k1,1 | awk '{print $1}' | uniq -c | awk '{print $2,$1}') <(sort -k1,1 population_h27sep) 大阪市北区 53 大阪市北区 117384 大阪市旭区 8 大阪市旭区 91169 大阪市港区 6 大阪市港区 82391 大阪市西区 28 大阪市西区 90712 ....
Q8
2回以上起きている場合、と聞いた時点でuniq -d
がピコーン!と閃くようになりたいですね。
閃かなかったのでuniq -c
でカウントしてawkで処理しました。
$ cat hittakuri | awk '{print $1,$2,$3,$8$9$10}' | sort | uniq -c | awk '$1>=2{$1="";print }'
ここでuniq -d
とセットになるオプションuniq -u
についても補足で説明をしておきました。
sort | uniq -c | sort -nr
など集計操作常套句についても改めておさらいをしました。
Q9
出題者上田さんの解答はawk
で未遂と未遂+既遂の2つの連想配列を使ってで処理されていました。
福岡では手段毎にデータをどうやって横に並べるかを解説しました。
xargs
を使った例はイカサマ臭いです。(手段毎で既遂と未遂のどちらかがゼロだとダメ)
$ self 7 5 hittakuri | sort | uniq -c | xargs -n 6 49 徒歩 既遂 3 徒歩 未遂 19 自動車 既遂 2 自動車 未遂 139 自転車 既遂 12 自転車 未遂 271 自動二輪 既遂 13 自動二輪 未遂
データを横に並べるのに、awk
の連想配列を使った場合と、Tukubaiのyarr
コマンドを使った場合です。
$ self 7 5 hittakuri | sort | uniq -c | awk '{a[$2]=a[$2]" "$1" "$3}END{for(v in a)print v,a[v]}' 徒歩 49 既遂 3 未遂 自動車 19 既遂 2 未遂 自転車 139 既遂 12 未遂 自動二輪 271 既遂 13 未遂 $ self 7 5 hittakuri | sort | uniq -c | self 2 3 1 | yarr num=1 徒歩 既遂 49 未遂 3 自動車 既遂 19 未遂 2 自転車 既遂 139 未遂 12 自動二輪 既遂 271 未遂 13
終了後
終了後はbash
のプロセス置換について使い方を解説したりした後、TLネタを話しました。
BSD版date
コマンドとBSD環境でビルドされたmawk
の致命的バグ!?案件です。
あとless
の話題も上がったので、下記のブログを紹介しました。
「巨大なファイルをviで開いてホストが沈黙する」という事例は皆さん結構体験されているようで・・・
-S
や-X
や-N
オプションは要チェックかと思います。