「第23回シェル芸勉強会 大阪サテライト」の記録

はじめに

6月18日(土)に開催された「第23回シェル芸勉強会 大阪サテライト」に参加しました。

朝10時から夜19時まで、湿度ではなく熱気に満ちたシェル漬けの一日でした。

なお、この記録は自分のような「普通の人」が自分以外の「普通の人」向けに「CLI、見た目地味やけど結構使えるでー」と紹介する目的で書いたため、プロの人にとっては物足りない記事になっているかも知れません(間違いの指摘等のツッコミは歓迎いたします)。

ちなみに、勉強会前日の夕食は深川めしでした(どうでもいい)。

メイン会場および他サテライト会場:

各会場の様子:

問題ならびに模範解答:

ライブストリーム(YouTube):


午前の部: 勉強会

  1. FreeBSDのブートプロセス
    • 講師: 今泉光之(@bsdhack)さん (USP友の会 BSD担当)
      • Intel製CPUのリアルモードとリアルモードについて
      • マスターブートレコードについて
      • boot0・boot1・boot2・loader・kernelについて
      • init・/etc/rcについて
  2. シェル芸入門
    • 講師: 石井久治(@hisaharu)さん (USP友の会)
      • シェルの動作モードは、インタラクティブモードとシェルスクリプトの2種類
      • シェルスクリプト≠シェル芸
      • 持ち技の多さが一撃の幅を広げる
      • シェル芸勉強会に参加して知らない機能が出てきたら何か1つを身に付けて帰る
      • UNIX哲学について

午後の部: シェル芸勉強会

  • いずれの問題も、Debian 8「jessie」の32ビットPC版で解答しています。
  • 使用OSによっては、sortを実行する言語環境をLANG=Cにする必要があるかも知れません。

Q1

問題:

$ curl http://www.data.jma.go.jp/fcd/yoho/typhoon/statistics/landing/landing.csv | nkf -wLux > landing.csv

で生成したlanding.csvから、第1フィールドが年月、第2フィールドが台風の上陸頻度となっているmonthly_typhoonを生成します。

$ cat monthly_typhoon
195101 0
195102 0
195103 0
195104 0
195105 0
(...中略...)
201508 1
201509 1
201510 0
201511 0
201512 0

解答:

$ cat landing.csv | sed '1d' | sed 's/,/, /g' | sed 's/ ,/ 0,/g' | sed 's/, $/, 0/' | awk -F', ' '{for(m=2;m<=13;m++){printf "%04d%02d %d\n",$1,m-1,$m}}' > monthly_typhoon
$ cat monthly_typhoon
195101 0
195102 0
195103 0
195104 0
195105 0
(...中略...)
201508 1
201509 1
201510 0
201511 0
201512 0

説明:

landing.csvの内容は

$ cat landing.csv | head
年,1月,2月,3月,4月,5月,6月,7月,8月,9月,10月,11月,12月,年間
1951,,,,,,,1,,,1,,,2
1952,,,,,,1,1,1,,,,,3
1953,,,,,,1,,,1,,,,2
1954,,,,,,,,1,4,,,,5
(...中略...)
2011,,,,,,,1,,2,,,,3
2012,,,,,,1,,,1,,,,2
2013,,,,,,,,,2,,,,2
2014,,,,,,,1,1,,2,,,4
2015,,,,,,,2,1,1,,,,4

のようになっています。

まず、1行目を削除して空文字列のフィールドに「0」を挿入します。

$ cat landing.csv | sed '1d' | sed 's/,/, /g' | sed 's/ ,/ 0,/g' | sed 's/, $/, 0/'
1951, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 2
1952, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 3
1953, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 2
1954, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 5
1955, 0, 0, 0, 0, 0, 0, 1, 0, 1, 2, 0, 0, 4
(...以下略...)

そして、第2フィールドから第13フィールドまでをawkで整形してmonthly_typhoonにリダイレクトします。

Q2

問題:

Q1で生成したmonthly_typhoonから年ごとの台風の上陸頻度を集計し、生成元のlanding.csvの最終フィールドの値と比較してデータが妥当か確認します。

解答:

$ diff -sq <(sed 's/\(....\)\(..\)/\1 \2/' monthly_typhoon | awk '{m[$1]+=$3}END{for(y in m)print y,m[y]}') <(sed '1d' landing.csv | sed 's/,$/0/' | sed 's/^\(....\),.*,\([0-9]\+\)$/\1 \2/')
ファイル /dev/fd/63 と /dev/fd/62 は同一です

説明:

monthly_typhoonからは、年をキーとした連想配列を使って年ごとの台風の上陸頻度を集計します。

$ sed 's/\(....\)\(..\)/\1 \2/' monthly_typhoon | awk '{m[$1]+=$3}END{for(y in m)print y,m[y]}'
1951 2
1952 3
1953 2
1954 5
1955 4
(...以下略...)

また、landing.csvからは、第1フィールドと最終フィールド(空文字列は「0」に置換)を抽出します。

$ sed '1d' landing.csv | sed 's/,$/0/' | sed 's/^\(....\),.*,\([0-9]\+\)$/\1 \2/'
1951 2
1952 3
1953 2
1954 5
1955 4
(...以下略...)

そして、これら2つのシェル芸を解答にあるように、プロセス置換を利用してdiff -sqに渡します。

なお、diffコマンドの-sおよび-qオプションの効果は次の通りです。

-q, –brief
    ファイルが違うかどうかだけを報告する。 違いの詳細は報告しない。

-s, –report-identical-files
    2 つのファイルが同じだったときも報告する。

Q3

問題:

各月に台風が上陸する確率を求めます。

解答:

$ sed 's/^\(....\)\(..\)/\1 \2/' monthly_typhoon | awk '{c[$2]+=1;l[$2]+=($3>0?1:0)}END{print "  月  件 確率";for(m in l)print m"月",l[m]"件",l[m]/c[m]}' | sed 's/^0/ /' | sort
  月  件 確率
 1月 0件 0
 2月 0件 0
 3月 0件 0
 4月 1件 0.0153846
 5月 2件 0.0307692
 6月 9件 0.138462
 7月 26件 0.4
 8月 41件 0.630769
 9月 41件 0.630769
10月 13件 0.2
11月 1件 0.0153846
12月 0件 0

説明:

月をキーとしたawkの連想配列を2つ用意します。計算する時に各月の分母になるものと、各月に台風が上陸したかどうかを格納するものです。なお、加算されていく後者の値は上陸していれば「1」を、上陸していなければ「0」となります。

パターンENDでヘッダ行を出力して計算後、sedおよびsortで見やすくなるように整形します。

Q4

問題:

各年で最初に台風が上陸した月を求め、どの月が何回だったかを集計します。

解答:

$ sed 's/^..../& /' monthly_typhoon | sed '/ 0$/d' | sort -r | awk '{m[$1]=$2}END{for(y in m)print m[y]}' | sort | uniq -c | awk 'BEGIN{print "月 回"}{print $2,$1}'
月 回
04 1
05 2
06 9
07 21
08 19
09 7
10 2

説明:

時間切れで当日に解答できなかった問題です。

年をキーとしたawkの連想配列の値に、逆順に加工したmonthly_typhoonの第3フィールドの値を代入していくと、結果として最初に台風が上陸した月が代入されることになります。

sortした後、出現回数を出力する-cオプションを付けてuniqします。

好みに応じてawkを使って出力を整形するのもよいでしょう。

Q5

問題:

台風が上陸しなかった年を抽出します。

解答:

$ sed -e '/^[0-9]\{4\},\+$/!d' -e 's/,//g' landing.csv
1984
1986
2000
2008

説明:

landing.csvで、年以降は行末まで「,」になっている行をsedで抽出します。

なお、monthly_typhoonを使う場合は、年をキー、第3フィールドの総和を値としたawkの連想配列を利用します。

$ sed 's/^..../& /' monthly_typhoon | awk '{l[$1]+=$3}END{for(y in l){if(l[y]==0)print y}}'
1984
1986
2000
2008

Q6

問題:

$ curl http://www.city.osaka.lg.jp/shimin/cmsfiles/contents/0000298/298810/006hittakuri2015.csv | nkf -wLux | tr , ' ' | tail -n +2 > hittakuri

で生成したhittakuriについて、各区で何件のレコードがあるかを調べます。

ちなみに、hittakuriの内容は

$ cat hittakuri
大阪市北区 曾根崎 1丁目付近 窃盗 既遂 ひったくり 自動二輪 2015年 1月 24日 2時頃 女性 20代
大阪市北区 兎我野町 付近 窃盗 既遂 ひったくり 自動二輪 2015年 2月 11日 20時頃 女性 20代
大阪市北区 曾根崎 2丁目付近 窃盗 既遂 ひったくり 自動二輪 2015年 4月 13日 3時頃 女性 20代
大阪市北区 曾根崎 2丁目付近 窃盗 既遂 ひったくり 自動二輪 2015年 4月 13日 2時頃 女性 40代
大阪市北区 角田町 付近 窃盗 既遂 ひったくり 自動二輪 2015年 4月 7日 3時頃 女性 20代
(...以下略...)

のようになっています。

解答:

$ cat hittakuri | awk '{rec[$1]+=1}END{for(ku in rec)print ku,rec[ku]}' | sort
大阪市阿倍野区 19
大阪市旭区 8
大阪市港区 6
大阪市此花区 1
大阪市住吉区 29
(...以下略...)

説明:

第1フィールドに格納されている区名をキーとしたawkの連想配列を使って、各区のレコード数を計算します。

-dオプションで区切り文字を、-fオプションでフィールド番号を指定したcutコマンドで第1フィールドのみを抽出し、Q4と同様にsort後にuniq -cするという別解もあります。

$ cut -d' ' -f1 hittakuri | sort | uniq -c
     19 大阪市阿倍野区
      8 大阪市旭区
      6 大阪市港区
      1 大阪市此花区
     29 大阪市住吉区
     (...以下略...)

Q7

問題:

各区の人口データpopulation_h27sepを使って、各区の人口当たりのひったくり件数のランキングを作成します。

解答:

$ paste <(sort population_h27sep) <(cut -d' ' -f1 hittakuri | sort | uniq -c) | awk '{print $1,$3/$2*100"%"}' | sort -k2,2r | cat -n
     1  大阪市中央区 0.0592467%
     2  大阪市北区 0.045151%
     3  大阪市浪速区 0.0343219%
     4  大阪市西成区 0.0338431%
     5  大阪市西区 0.0308669%
     (...中略...)
    20  大阪市西淀川区 0.00823003%
    21  大阪市港区 0.00728235%
    22  大阪市住之江区 0.00725105%
    23  大阪市鶴見区 0.00530631%
    24  大阪市此花区 0.0014723%

説明:

2つのプロセス置換を行う際に、データの順番がずれないようにsortしたpopulation_h27sepと、Q6の別解のシェル芸をプロセス置換を利用してpasteに渡すと

$ paste <(sort population_h27sep) <(cut -d' ' -f1 hittakuri | sort | uniq -c)
大阪市阿倍野区 107791        19 大阪市阿倍野区
大阪市旭区 91169              8 大阪市旭区
大阪市港区 82391              6 大阪市港区
大阪市此花区 67921            1 大阪市此花区
大阪市住吉区 154217          29 大阪市住吉区
(...以下略...)

のようになります。

awkで各区の人口当たりのひったくり件数を計算した後、第2フィールドの値で逆順にsortしてcat -nで行番号を順位の代わりに表示します。

Q8

問題:

同じ住所かつ同じ日付で2件以上のひったくりがあった場合について、その住所および日付を出力します。

解答:

$ awk '{print $1,$2,$3,$8,$9,$10}' hittakuri | sort | uniq -d
大阪市北区 角田町 付近 2015年 11月 4日
大阪市北区 曾根崎 2丁目付近 2015年 4月 13日
大阪市淀川区 十三本町 1丁目付近 2015年 4月 16日

説明:

awkで住所(第1・2・3フィールド)と日付(第8・9・10フィールド)を抜き出した後、sortしてuniq -dします。

ちなみに、uniqコマンドの-dオプションの効果は次の通りです。

-d, –repeated
    グループ毎に、重複した行のみ出力する

Q9

問題:

ひったくりの手段とその成功率(既遂の率)を求めます。

解答:

$ awk '{t[$7]+=1;if($5=="既遂")s[$7]+=1}END{for(m in t)print m,s[m]/t[m]}' hittakuri
徒歩 0.942308
自動車 0.904762
自転車 0.92053
自動二輪 0.954225

説明:

時間切れで当日に解答できなかった問題です。

第7フィールドに格納されているひったくりの手段をキーとした連想配列を2つ用意します。tには手段ごとのトータル、sには手段ごとの成功(既遂)数が値として代入されます。

パターンENDで成功率を計算し、出力します。


懇親会&LT大会

  1. CLOUDATCOST(2)
    • 発表者: MSR(@msr386)さん
      • 前回に続き、激安VPSのCloudAtCostの話題
      • 現在、恒常的に「80%OFF」の表示
      • 新データセンター、DC-3がオープン
      • 新規ユーザ紹介でCroudPro1を無料提供
  2. sedのデバッガ(sdb)
    • 発表者: 田窪守雄(@takubo_morio)さん
      • サーバとして動作(TCP/IP)
      • クライアントはAWK製
      • Vim上でも動作
      • 使用感はgdb風
  3. コマンドプロンプト芸
  4. ExSQell = Excel + SQL + Shell
    • 発表者: nmrmsys(@nmrmsys)さん
      • SQL文もシェルコマンドも、Excel上で実行可
      • シェル芸勉強会にも、ExSQellで参戦
      • シェルコマンドはCygwinで処理
      • Bash on Ubuntu on Windowsについては現時点では未サポート
  5. AWS Lambda でシェル芸
    • 発表者: so(@3socha)さん
      • lambdashを使えば、AWS Lambda上でシェル芸ができる
      • 書き込み可能ディレクトリは/tmpのみ
      • Amazon Linuxにあるものはほとんど使える(存在しないコマンドはzipで固めてアップロード)
      • コマンド実行ごとにコンテナが立ち上がる(放っておくとそのうち消える)
      • forkbombはタイムアウト(?)

おわりに

省庁および自治体のオープンデータを分析する手法を学ぶ、というのが今回の勉強会のテーマだったのではないかと思います。

CLIとテキストデータの組み合わせという一見「?」な手法でも、表計算ソフトを使った場合と遜色ない作業ができることが分かりました。

あと、前回よりはAWKの連想配列の使い方に慣れてきたような気がします。あくまでもそういう気がするだけですが。

最後に、

に改めてお礼を申し上げます。

Written on June 24, 2016