ということで今回は快挙である!ついに会場の様子を写真に撮ることが出来たのである・・・こんなことで感無量な気持ちになれるオイラは小せえよ。今回の会場はいつもの会場とは違って、会社の会議スペースを利用。参加者が少なかったのでテレビを使った小ぢんまりな雰囲気。
会場の理想は中立の立場になれる場所の方がいいなあと思っているのだが、会場がなかなか確保出来なかったので。まあたまにはこんなのもええかもしれん。参加者数は自分も含めてのべ3名。1名は午前中のみで1名は午前の途中からと午後にかけて。午後に参加された方は熊本から遠路はるばる参加いただいた。
イベント案内ページ
本家東京会場
大阪サテライト会場
福岡サテライト会場
イベント関連ページ
リンク集
第24回◯◯o◯裏番組シェル芸勉強会リンク集 – 上田ブログ
大阪サテライト会場レポート
Togetterおまとめ
開始前
今回はすぐに午前の部が始まったので、特にイントロは話さず。一応こんなの準備はしていたのだが使わず。 speakerdeck.com
午前の部
今回午前の部は初心者向けに偽りなし!?な感じだった。前半は上田会長によるawk入門編。manの読み方から基本的な使い方の説明があった。
後半はgreymdさんによる初心者向け問題が展開されていった。
毎日叩ける シェル芸を覚えよう! // Speaker Deck
午前の部にいらっしゃった方はawkやテキスト処理にあまり慣れていらっしゃらないので、私が補足説明をしながらこちらのペースで進めて丁度いい感じだった。余談でデータベースを使う場合にCUIでは下記のような感じで処理出来る、ってのをお話ししたり。CUIとGUIのそれぞれの良さがあるということが面白いと思っていただけたようだ。
$ echo 'SELECT * FROM users;' | mysql 接続先サーバ
お昼は近所の博多ラーメンへ。長いこと行ってなかったので久しぶりに美味しかった。長浜ラーメンはシオカライぜとか、福岡市内で豚骨ラーメン以外はなかなか流行らんのうとか、そもそもラーメン自体酸化した脂肪だから体にワルイやろ!と身も蓋もないこととか。
午後の部
人数は減って私ともう一人の2名でさしの勝負!?の時間となった。いよいよ本番へ突入。ひどい問題という割には?比較的マトモな印象であったような??
ひどい問題が8問完成しますた。| 第6回もう初心者向けでないなんて言わないよ絶対午前のシェル勉強会/第24回◯◯o◯裏番組シェル芸勉強会 (08月27日) https://t.co/Hej33M8yW1 #シェル芸
— Ryuichi Ueda (@ryuichiueda) August 24, 2016
問題と解答のページは、上田さんブログの記事を参照してもらいたい。
【問題と解答】第24回◯◯o◯裏番組シェル芸勉強会 – 上田ブログ
ではここから福岡サテライト会場で解説した内容を。参加者の方がTukubaiコマンドの使い方に興味があるということもあり、Tukubaiコマンドを使った解答をメインに作成してみた。まあ私の場合Tukubai使った方が楽な場合が多い気がするので。
解答例はMacで作成。GNU版のコマンドはguniqなど頭にgが付くコマンドになっている場合がある。
Q1
横方向つまり行毎での集計な問題。Tukubai的解答がこちら。まずは行番号付けて縦に並べて整列の儀。
$ cat Q1 | juni | tarr num=1 | sort 1 卵 1 玉子 1 玉子 1 玉子 1 玉子 ... 4 卵 4 玉子 5 卵 5 玉子 5 玉子
次に行毎の重複を数え上げて横に並べ替える。3列目と5列目が重複の数になっている。
$ cat Q1 | juni | tarr num=1 | sort | count 1 2 | yarr num=1 1 卵 1 玉子 5 2 卵 3 玉子 3 3 卵 2 玉子 4 4 卵 5 玉子 1 5 卵 1 玉子 2
最後はawkで整形して完成。
$ cat Q1 | juni | tarr num=1 | sort | count 1 2 | yarr num=1 | awk '{print $4":"$5,$2":"$3}' 玉子:5 卵:1 玉子:3 卵:3 玉子:4 卵:2 玉子:1 卵:5 玉子:2 卵:1
次にオーソドックスに?awkを使った解答。だがしかし最初はうっかりミス。連想配列を行毎に初期化するのを忘れて累計が出てしまってる・・・
$ cat Q1 | awk '{for(i=1;i<=NF;i++){a[$i]++};for(v in a){printf v":"a[v]" "};print ""}' 玉子:5 卵:1 玉子:8 卵:4 玉子:12 卵:6 玉子:13 卵:11 玉子:15 卵:12
初期化をどうするか?awkにdelete()
という関数があるようなので利用して正解。
$ cat Q1 | awk '{for(i=1;i<=NF;i++){a[$i]++};for(v in a){printf v":"a[v]" "};print "";delete(a)}' 玉子:5 卵:1 玉子:3 卵:3 玉子:4 卵:2 玉子:1 卵:5 玉子:2 卵:1
参考にさせていただいたツイート。
$ cat Q1 |awk '{for(i=1;i<=NF;i++){count[$i]++};for(key in count){printf key":"count[key]" "}print "";delete(count)}'#シェル芸
— むっつー (@mutz0623) August 27, 2016
Q2
2回目以降の重複を削除する問題。これもTukubai芸の解答で。縦に並べて行番号を付けておくの儀。
$ cat Q2 | grep -o . | juni 1 へ 2 の 3 へ 4 の 5 も 6 へ 7 じ
次に列を入れ替えて並べ替え。そして各文字毎に1行目を取得。
$ cat Q2 | grep -o . | juni | self 2 1 | sort -k1,1 | getfirst 1 1 じ 7 の 2 へ 1 も 5
後は2列目をキーにして並べ替え、1列目だけを選べば完成。
$ cat Q2 | grep -o . | juni | self 2 1 | sort -k1,1 | getfirst 1 1 | sort -k2,2n | self 1 | tr -d '\n' へのもじ
会長のawkを使った解答の補足。下記の実行結果を見れば動きが理解しやすいかも。!a[$1]
とa[$1]=1
の組み合わせがポイントで、!a[$1]
が真(つまり1)になった時に出力されている。
$ cat Q2 | grep -o . | awk '{print $1,!a[$1];a[$1]=1}' へ 1 の 1 へ 0 の 0 も 1 へ 0 じ 1
この考え方を更に発展させた解答例を後で思いついた。まずはこちらを。
$ cat Q2 | grep -o . | awk '{print $1,!a[$1]++}' へ 1 の 1 へ 0 の 0 も 1 へ 0 じ 1
つまり!a[$1]++
が真の時に出力すれば良い。
$ cat Q2 | grep -o . | awk '!a[$1]++' へ の も じ
この考え方は、シェルスクリプトマガジンvol.39に載っていたAWK入門での記事を参考にした。
Q3
これは色々ゴニョゴニョやってみたのだがうまくいかず。とりあえずGNU版uniqの--group=both
というオプションにびっくりポン!
上田会長のawkを使った解答に補足。下記の動きを見てもらえばif($1!=a)
とa=$1
の組み合わせがどう働いているか分かりやすいかも。1列目のデータが変わる行のところで$1!=a
が真になるのを利用している。
$ sort Q3 | awk '{print $1!=a,$0;a=$1}' 1 金 日成 0 金 正日 0 金 正男 1 キム タオル 0 キム ワイプ
Q4
Personal Tukubai の反則芸で、何の苦労もナッシングゥ!Personal Tukubaiではxlsx形式のファイルが読み書き出来るコマンドが使えるのだ。
$ rexcelx 1 A1 A1 Q4.xlsx 114514
補足だが、xlsx形式のファイルはXML形式になった複数のファイルが一つのzipファイルに固められたものだ。fileコマンドで確認するとzip形式のファイルということが確認出来る。
$ file Q4.xlsx Q4.xlsx: Zip archive data, at least v2.0 to extract
unzipコマンドで-l
オプションを使うと、zipファイルの中身一覧が確認出来る。xl/worksheets/sheet1.xml
というファイルに、セルの中のデータが記載されている。
$ unzip -l Q4/Q4.xlsx Archive: Q4/Q4.xlsx Length Date Time Name -------- ---- ---- ---- 1220 01-01-80 00:00 [Content_Types].xml 733 01-01-80 00:00 _rels/.rels 698 01-01-80 00:00 xl/_rels/workbook.xml.rels 745 01-01-80 00:00 xl/workbook.xml 795 01-01-80 00:00 xl/sharedStrings.xml 7646 01-01-80 00:00 xl/theme/theme1.xml 1210 01-01-80 00:00 xl/styles.xml 1420 01-01-80 00:00 xl/worksheets/sheet1.xml 22300 01-01-80 00:00 docProps/thumbnail.jpeg 617 01-01-80 00:00 docProps/core.xml 803 01-01-80 00:00 docProps/app.xml -------- ------- 38187 11 files
ということでこれは上田会長のエクシェル芸問題。詳しくは下記のブログや「シェルプログラミング実用テクニック」の本を参考に。会長の解答例にあるhxselectというコマンドは、html-xml-utils というのに付属している。UbuntuやMacな方はパッケージでインストールしてみよう。
unzip -p で真のエクシェル芸が完成したような気がする。 – 上田ブログ
Q5
数式が書いてあるテンプレートに値を代入するような問題。まあとりあえずは下記のように無難にtrコマンドで代入処理。
$ tr 'x' '2' < Q5 2 + 2^2 2 + 1/2 2*2*2
後はbcコマンドに渡す。-l
オプションは浮動小数点計算させるため。
$ tr 'x' '2' < Q5 | bc -l 6 2.50000000000000000000 8
次にecho 2 |
で始める場合。これはコマンド列を生成するという発想にすればええだ!こんな感じで実行するコマンド列を作ってみる。
$ echo 2 | sed 's/./tr x & < Q5/' tr x 2 < Q5
後はシェルに投げて実行してbcに渡す。
$ echo 2 | sed 's/./tr x & < Q5/' | sh | bc -l 6 2.50000000000000000000 8
Q6
重複カウントと置換の複合問題といったところか。まずは前半で重複カウントをしておく。玉子の方が多いようだ。
$ cat Q6 | grep -oE '(卵|玉子)' | sort | uniq -c | sort -k1,1n 12 卵 15 玉子
後はここからコマンド列を生成。sedを使って置換する準備運動。
$ cat Q6 | grep -oE '(卵|玉子)' | sort | uniq -c | sort -k1,1n | xargs | awk '{print "sed s/"$2"/"$4"/g Q6"}' sed s/卵/玉子/g Q6
後はシェルに渡して実行して華麗に終了。
$ cat Q6 | grep -oE '(卵|玉子)' | sort | uniq -c | sort -k1,1n | xargs | awk '{print "sed s/"$2"/"$4"/g Q6"}' | sh 玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子
または下記のようにsedスクリプトを生成し、
$ cat Q6 | grep -oE '(卵|玉子)' | sort | uniq -c | sort -k1,1n | xargs | awk '{print "s/"$2"/"$4"/g"}' s/卵/玉子/g
GNU sedの-f -
オプションで標準入力から受け取るやり方とかも。会長の解答例ではxargs使ってる。
$ cat Q6 | grep -oE '(卵|玉子)' | sort | uniq -c | sort -k1,1n | xargs | awk '{print "s/"$2"/"$4"/g"}' | gsed -f - Q6 玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子
Q7
さあ難しい領域に突入。色々とこねくり回してたけど敗北チーン。GNU awkにasort()
なんて関数があったのか。後は上田会長の発想力。
Q8
前半はまあ力技か。結局上田会長の解答通り。後半は素数の選択自体はfactorコマンド(Macの場合はgfactorコマンド)で簡単そうだが、計算元の数字を残した上で検索するには?grepの-f
オプションを使うかなあ?というところまでは思いついたのだが。
whileループを使った解答は上田会長の解答例に記載されているので、ここでは上田会長が解説してたgrepの-f
オプションを使った解答例を。
まずは検索元データの作成。これは
$ cat tmp | sed 's/./& /g' | awk '{print $0,$1*$2*$3*$4+$5*$6*$7}' 1 2 3 4 5 6 7 234 1 2 3 4 5 7 6 234 1 2 3 4 6 5 7 234 1 2 3 4 6 7 5 234 1 2 3 4 7 5 6 234 ..... 7 6 5 4 1 3 2 846 7 6 5 4 2 1 3 846 7 6 5 4 2 3 1 846 7 6 5 4 3 1 2 846 7 6 5 4 3 2 1 846
計算結果は846以下みたいなので、845以下の素数のリストを作っておく。正確性を上げるために正規表現にしておく。
$ seq 1 846 | gfactor | awk 'NF==2{print " "$2"$"}' | head 2$ 3$ 5$ 7$ 11$ ..... 821$ 823$ 827$ 829$ 839$
後は素数の正規表現リストをgrepの-f
オプションで渡してやれば良い。ここではbashのプロセス置換を利用している。
$ cat tmp | sed 's/./& /g' | awk '{print $0,$1*$2*$3*$4+$5*$6*$7}' | grep -f <(seq 1 846 | gfactor | awk 'NF==2{print " "$2"$"}') 2 3 4 6 1 5 7 179 2 3 4 6 1 7 5 179 2 3 4 6 5 1 7 179 2 3 4 6 5 7 1 179 2 3 4 6 7 1 5 179 ..... 6 4 3 2 1 7 5 179 6 4 3 2 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
ここで余談。sedで1文字ずつ分ける処理を使っているが、この処理は意外と負荷が高め。ここでawkで列の区切り文字(セパレータ)を表すFS
という特殊変数を空文字にすると、連続した文字を一つずつ列として処理してくれる。
$ cat tmp | awk '{print $0,$1*$2*$3*$4+$5*$6*$7}' FS= 1234567 234 1234576 234 1234657 234 1234675 234 1234756 234 ..... 7654132 846 7654213 846 7654231 846 7654312 846 7654321 846
これを使っても同様に結果が出る。
$ cat tmp | awk '{print $0,$1*$2*$3*$4+$5*$6*$7}' FS= | grep -f <(seq 1 846 | gfactor | awk 'NF==2{print " "$2"$"}') 2346157 179 2346175 179 2346517 179 2346571 179 2346715 179 ..... 6432175 179 6432517 179 6432571 179 6432715 179 6432751 179
上記のまとめである。
sed 's/./& /g'
で1文字ずつ分割する処理は負荷が高め。- awkで
FS=
を指定すると、1文字ずつフィールド分割して処理。
このawkでFS=
を使うやり方は、真・マイナンバーシェル芸の記事を書いていた時にTwitterで教えていただいた。
以上、今回も大変お疲れ様だった。
終了後
午後も参加いただいた方は、熊本から遠路はるばるいらっしゃった。ずいぶん前に福岡の勉強会でお会いしてから大変お久しぶりであった。TwitterでTukubaiコマンドに興味があるのを聞いていたので、どんなコマンドがあるかなど解説したり。
お話を聞いてみると、仕事での開発はC#でやることが多いらしいが、データ処理やお仕事の自動化では、シェルやLispを扱うことが多いらしい。S式が飛び交うAPIをwgetで叩いてるとか面白い。やっぱりシェルやテキスト処理は必要な素養なんだなあと改めて実感した次第。
またデータからコマンド列を作ってシェルにパイプで渡して実行したり、sedの命令やgrepの検索文字列をデータから作って渡す、という考え方も面白いと思っていただけたようだ。これが出来るとメタプログラミング的な要素をCUIで実現出来るので重要だと思うし、今回の問題もそういう意図があった気がしている。
1年くらい前から筋トレを本格的にされていて、漢も惚れそなカッチョイイ体。自分も最近少しながら筋トレ始めてるので、今後も色々参考にさせていただきたい。