日々之迷歩

世の中わからんことだらけ

ITが複雑で難しくなっていく様に翻弄される日々です。微力ながら共著させていただいた「シェル・ワンライナー160本ノック」をよろしくお願い申し上げます。

第23回シェル芸勉強会へ遠隔参加

イマイチ雨の降りが悪い梅雨の最中ですが、会場の様子を後ろから写真に撮るのがいつ実現出来るのかもう分からなくなってきた今日この頃です。シェル芸勉強会へ遠隔参加する福岡サテライト会場も、今回で7回目を数えることになりました。会場のご提供いただいているベータソフト様と管理ご担当者様、ありがとうございます。今回は午前中が別件の用事があったため、午後の部から参加することになりました。

問題出題者の上田さん、ありがとうございます。参加者の皆様お疲れ様でした。福岡サテライトの参加者はのべ9人でした。(途中出入有)初めての参加者は1名でした。

勉強会の情報

勉強会主催者の上田さんが公開されているページをご覧ください。 b.ueda.tech

開始前

福岡サテライト会場にも少しずつ顔なじみな方が増えてきたように思います。 ということで、僭越ながらSoftware Design 6月号のbash特集記事と、シェルスクリプトマガジンvol.38を宣伝させていただきました。

勉強会ライブ配信前のイントロで話したスライドはこちらです。時間が足りず7ページまでしか話せず。

speakerdeck.com

開始

オープンデータについては・・・残念ながらPDFとか画像とかXLSですよねー。久留米市で人口データが公開されていますが、HTMLのテーブルになっている分については、シェルスクリプトでスクレイピングしてCSVにする処理を以前ブログに書いたのでここで紹介しておきます。

papiro.hatenablog.jp

今回の問題は比較的正統派な問題が多かったように思います。初心者の方が多い福岡サテライト会場にはちょうどいい感じだったかもしれません。

福岡サテライト会場で解説した内容を記載します。完全に解けることよりも考え方や余談を重視するスタイルです。 解答例はmacOSで作成しました。GNU版のコマンドはguniqなど頭にgが付くコマンドになっている場合があります。

Q1

年月をキーにしてデータを縦に並べる処理。

とりあえずはawk一発芸で作成しました。出題者上田さんの解答のように、分割して処理した方が分かりやすいかもしれません。 福岡ではawkに慣れていない人が多かったので、-Fオプション、三項演算子、sprintfi$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のselfsm2を使うなどです。

$ 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

しかし福岡サテライト参加者から、天才的な解答が出てきました。「使うファイルって指定されてないですよね????」

あああ元の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の致命的バグ!?案件です。

speakerdeck.com

あとlessの話題も上がったので、下記のブログを紹介しました。 「巨大なファイルをviで開いてホストが沈黙する」という事例は皆さん結構体験されているようで・・・ -S-X-Nオプションは要チェックかと思います。

papiro.hatenablog.jp