信じてやってみる「第24回シェル芸勉強会 大阪サテライト」の復習

はじめに

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

今回も朝10時から夜19時までsh行三昧の一日でした。

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

各会場の様子

問題ならびに模範解答

ライブストリーム(YouTube)


午前の部: シェルに関する勉強会

  1. man gawkを1個ずつやってみる。
    • 講師: Ryuichi Ueda(@ryuichiueda)さん
      • gawkとは
      • パターンとアクション
      • -Fオプション
      • -vオプション
      • -fオプション
      • –traditionalまたは–compatオプション
      • -W lintオプション
      • –re-intervalオプション
      • BEGINブロック
      • 型について
      • 変数の初期化について
      • 配列について
  2. 毎日叩けるシェル芸を覚えよう!
    • 講師: ぐれさん(@grethlen)さん
      • シェル芸の学び方
      • ボトムアップなアプローチ
      • トップダウンなアプローチ
      • プチシェル芸勉強会
        • パターンマッチ(grep)
          解答例:
          awk '$1~/^2016/' holidays
        • 行数をカウント
          解答例:
          cat holidays | grep '^2016-02' | wc -l
        • 特定のフィールドを抽出
          解答例:
          awk '/^2016-09-19/{print $3}' holidays
        • コマンド置換
          解答例:
          grep $(date '+%Y-%m-%d') holidays
        • 数え上げ(uniq -c)
          解答例:
          awk '/^2016/{print $2}' holidays | sort | uniq -c
        • あるパターンからあるパターンまで抽出
          解答例:
          sed '/^2016-08-01/,/^2016-09-30/!d' holidays
        • プロセス置換
          解答例:
          diff <(awk '/^2015/{print $3}' holidays | sort | uniq) <(awk '/^2016/{print $3}' holidays | sort | uniq)
        • 上級者向け問題
          解答例:
          sed -rz 's/(20[^ ]+)( . [^ ]+ 1)\n(20[^ ]+)( . [^ ]+ 0)\n(20[^ ]+)( . [^ ]+ 1)/\1 \3 \5!\n/g' holidays | sed '/!$/!d' | sed 's/!$//'
      • 参考文献・支援ツール

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

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

Q1: 行毎の集計

$ awk '{for(i=1;i<=NF;i++)$i=="玉子"?a+=1:b+=1;print "玉子:"a" 卵:"b;a=b=0}' Q1
玉子:5 卵:1
玉子:3 卵:3
玉子:4 卵:2
玉子:1 卵:5
玉子:2 卵:1

各行での処理の最後に、変数aおよびbの値を「0」でリセットしておく必要があります。

Q2: 2つ目以降の重複した文字を削除

$ grep -o . Q2 | awk '!a[$1]++' | sed -z 's/\n//g;s/$/\n/'
へのもじ

まず、grep -o .で1行1文字にて出力します。

そして、awk ‘!a[$1]++’で最初に出現したものだけを抜き出します。

Q3: 第1フィールドをキーとしたレコード区切り

$ sort -k1,1 Q3 | awk '{print(p==$1?$0:"%%\n"$0);p=$1}END{print "%%"}'
%%
キム タオル
キム ワイプ
%%
金 正男
金 正日
金 日成
%%

まず、sort -k1,1で第1フィールドをキーとしてソートします。

そして、第1フィールドの値を保存したawkの変数pを使い、第1フィールドの値が変わったかを確認します。

Q4: Excelファイルからのデータ抽出

$ libreoffice --invisible --convert-to html Q4.xlsx >/dev/null; w3m -dump Q4.html | awk 'NR==1{print $1}'
114514
$ libreoffice --invisible --convert-to html Q4.xlsx >/dev/null; w3m -dump Q4.html | awk 'NR==4{print $1}'
エクシェル芸

LibreOfficeはCLIでも使える、ということでExcelファイルをHTMLファイルに変換してデータを抽出します。

unzip -pを使っていないので「エクシェル芸」とすれば邪道だとは思いますが、手軽かつ古い形式のExcelファイルも扱うことが可能ということで紹介しました。

ちなみに、unzip -pを使った別解は次のようになります。

$ unzip -p Q4.xlsx xl/worksheets/sheet1.xml | sed -rz 's/^.*(<c r="A1"[^>]*><v>[^<]+<\/v><\/c>).*/\1/' | sed -r 's/^.*>([^<]+)<.*/\1\n/'
114514
$ unzip -p Q4.xlsx xl/sharedStrings.xml | sed -rz 's/<\/si><si>/\n/g' | grep '^<t>' | sed "$(unzip -p Q4.xlsx xl/worksheets/sheet1.xml | sed -rz 's/^.*(<c r="A4"[^>]*><v>[^<]+<\/v><\/c>).*/\1/' | sed -r 's/^.*>([^<]+)<.*/\1\n/')"'!d' | sed -r 's/^<t>([^<]+)<.*/\1/'
エクシェル芸

unzip -pのあとは、sedを使って不要な部分を削っていけばOKです。

Q5: 数式のテンプレートファイルを使った計算

$ echo 2 | xargs -I@ bash -c 'sed "s/x/("@")/g" Q5 | bc -l'
6
2.50000000000000000000
8

echoの出力をxargs -Iに渡し(後の「@」は置換文字列)、生成したコマンド文字列をbashで処理します。

なお、コマンド文字列の最後にあるbcは小数を扱えるように-lオプションを指定しています。

Q6: 単語数の多い方で置換

訂正: 当日の解答は「玉子」の数を1つ多く出力してしまう誤った解答であったため、次のようなワンライナーに訂正します。

$ mecab Q6 | sed '$d' | awk '{a[$1]+=1}END{if(a["玉子"]>=a["卵"]){for(i=1;i<=NR;i++)printf "玉子"}else{for(i=1;i<=NR;i++)printf "卵"}printf "\n"}'
玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子

最初に、形態素解析器mecabを使って「玉子」と「卵」を分別します。 次に、sedで次の処理で不要になる最終行を削除します。 最後に、awkの連想配列aで「玉子」と「卵」の数を数え、ENDブロックで「玉子」と「卵」のうち数が多いほう(ここでは「玉子」)をmecabおよびsedで生成したレコードの数に合わせて出力します。

Q7: 数字が昇順になっている行のみ出力

当日に解答できなかった問題です。

seq -wで得られた各行の数字について

  1. 「54321」から「12345」のように、各桁の数字を左から右へと昇順で並べ直す
  2. 各行の並べ直した数字を昇順でソート
  3. 重複した行を削除

すれば良いと考え、

$ seq -w 00000 99999 | xargs -I@ bash -c 'echo @ | grep -o . | sort | sed -z "s/\n//g;s/$/\n/"' | sort | uniq

としたものの処理が非常に重く、5桁から3桁に減らしても処理時間は

$ time seq -w 000 999 | xargs -I@ bash -c 'echo @ | grep -o . | sort | sed -z "s/\n//g;s/$/\n/"' | sort | uniq
000
001
002
(...中略...)
889
899
999

real    0m8.604s
user    0m0.048s
sys     0m0.108s

のように約9秒を要するため、

  1. 「11233」のように、全ての桁の数字について「左の桁の数字 <= 右の桁の数字」となっている行のみ出力

と考え方を切り替え、試しに3桁で処理時間を測ると

$ time seq -w 000 999 | sed -r 's/(.)/\1 /g' | awk '$1<=$2&&$2<=$3' | sed 's/ //g'
000
001
002
(...中略...)
889
899
999

real    0m0.017s
user    0m0.004s
sys     0m0.004s

のように圧倒的に良い結果が得られました。

結論として、最終的な解答としては

$ seq -w 00000 99999 | sed -r 's/(.)/\1 /g' | awk '$1<=$2&&$2<=$3&&$3<=$4&&$4<=$5' | sed 's/ //g'
00000
00001
00002
(...中略...)
88999
89999
99999

のようになります。

Q8: 日本数学オリンピック予選問題

Q8-1・Q8-2のいずれも、当日に解答できなかった問題です。

Q8-1: 1〜7を全て含む7桁の整数を列挙

まず、seqで「1234567」から「7654321」まで出力します。

そして、grep 1からgrep 7までを順にパイプでつないだものでふるいに掛けます。

$ awk 'BEGIN{printf "seq 1234567 7654321 ";for(n=1;n<=7;n++)printf "| grep "n" "}' | sh > tmp

grep 1からgrep 7まで書いていくと手間がかかるため、ここではawkでコマンド文字列を生成してshに渡しています。

Q8-2: 条件に合致する素数と各桁の数字を求める

$ paste <(sed -r 's/(.)/\1 /g' tmp) <(sed -r 's/(.)/\1 /g' tmp | awk '{print $1"*"$2"*"$3"*"$4"+"$5"*"$6"*"$7}' | bc | factor) | awk 'NF==9' | sed 's/:.*$//'
2 3 4 6 1 5 7 	179
2 3 4 6 1 7 5 	179
2 3 4 6 5 1 7 	179
(...中略...)
6 4 3 2 5 7 1 	179
6 4 3 2 7 1 5 	179
6 4 3 2 7 5 1 	179

生成したファイルtmpの各行にある、1〜7の全ての数字を含む7桁の整数「abcdefg」について「a * b * c * d + e * f * g = 素数」であるような行を抽出します。


LT大会・懇親会

  1. シェル芸でもIoTがしたい!
    • 発表者: nmrmsys(@nmrmsys)さん
      • Sphero SPRK+をCLIで操作
      • Raspberry PiにNode.js環境を構築
      • オレオレCLIは基本
      • デモの実行(sphero discoコマンドなど)
      • 今後の展望など
  2. お気軽!お手軽!スクレイピング!
    • 発表者: T.Motooka(@t_motooka)さん
      • スクレイピングとは
      • 実例として、金融機関コード・支店コード・各名称を取得
      • 対象サイトは大人の事情で秘密
      • PhantomJSでDOM操作して標準出力
  3. UTF-8テキストからUnicodeのコードポイントを求める@シェル芸
    • 発表者: 小原一哉(@KoharaKazuya)さん
      • UTF-8のコードポイントとは
      • UTF-8は可変長バイト
      • foldで折り返し
      • dcで基数の変換
  4. QRコードシェル芸?
    • 発表者: MSR(@msr386)さん
      • QRコードとは
      • qrencodeで生成
      • zbarimgおよびzbarcamで読み取り
      • シェル芸への応用
      • 実例として、危険シェル芸をQRコードに格納
  5. Bash on Windows環境非破壊ハンズオン
    • 発表者: くんすと(@kunst1080)さん
      • Bash on Windowsをどこまで破壊しても大丈夫なのか確認
      • /usrを破壊→大丈夫
      • /binを破壊→大丈夫
      • /libを破壊→大丈夫
      • /etcを破壊→大丈夫
      • これ以上の破壊活動は危険!!

おわりに

午前の部の前半でブリーフィング、後半で訓練、午後の部で実戦といった感じでした。

午前の部では、プチシェル芸勉強会の問題の難度調整が良い塩梅になっていて、シェル芸未体験の人でもライブストリームを一通り見て実際に手を動かせばシェル芸の手軽さと便利さを感じることができるのではないかと思います。

午後の部は相変わらず問題の難度が高く、特にQ7とQ8では中学・高校のテストで50点以上だったことがない自分の数学力の低さを改めて知ることになりました。

あと、モノの動き方を知るにはとりあえず壊してみるのが一番の近道なのではないか、と少々危険なことを思い浮かべました。

最後に、

に改めてお礼を申し上げます。ありがとうございました。

Written on August 31, 2016