日々之迷歩

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

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

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

会場の様子を後ろから写真に撮る、いつになったら実現出来るのかもう分からなくなってきた今日この頃。イマイチ雨の降りが悪い梅雨の最中、脳みそスパルタ教育にご参加いただきありがたや。

シェル芸勉強会へ遠隔参加する福岡サテライト会場も、今回で7回目を数えることになった。参加者の方々と会場のご提供いただいているベータソフト様と管理担当者、そしてもちろんUSP友の会運営陣と問題作成会長のおかげである。今回は午前中が別件の用事があったため、午後の部から参加することになった。

参加者はのべ9人だった。(途中出入有)初めての参加者は1名。福岡のペースで考え方や解説、余談を重視して進めるスタイルで。

イベント案内ページ

本家東京会場

usptomo.doorkeeper.jp

大阪サテライト会場

5f01b3bc1d81c1fae2378cdc89.doorkeeper.jp

福岡サテライト会場

atnd.org

Twitterおまとめ

上田会長によるTogetterおまとめ。

togetter.com

開始前

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

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

speakerdeck.com

開始

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

papiro.hatenablog.jp

今回の問題は変態チックな問題ではなく、比較的正統派な問題が多かった。だが手練れが少ない福岡サテライト会場にはちょうどいい感じだったかもしれない。

問題と解答のページは、上田さんブログの記事を参照してもらいたい。

【問題と解答】第23回梅雨でモワッとしたシェル芸勉強会 – 上田ブログ

ではここから福岡サテライト会場で解説した内容を。完全に解けることよりも考え方や余談を重視するスタイル。

解答例はMacで作成。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

勉強会の時には話さなかったが、Macや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で確率を計算すれば良い。

$ 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を使った場合(Macでは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つの連想配列を使うことで処理している。福岡では手段毎にデータをどうやって横に並べるかを解説した。あとはawkで計算するだけなので。

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

ということで今回も無事終了した。初めていらっしゃった方にも、何かしらのヒントが伝わっているといいなあと思う。東京、大阪、福岡、三ヶ所同時開催の勉強会、皆様お疲れ様!