読者です 読者をやめる 読者になる 読者になる

日々之迷歩

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

ITが複雑で難しくなっていく様に翻弄される日々です

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

awk Shell シェル芸 Tukubai

ということで今回は快挙である!ついに会場の様子を写真に撮ることが出来たのである・・・こんなことで感無量な気持ちになれるオイラは小せえよ。今回の会場はいつもの会場とは違って、会社の会議スペースを利用。参加者が少なかったのでテレビを使った小ぢんまりな雰囲気。

f:id:papiro:20160827202311j:plain

会場の理想は中立の立場になれる場所の方がいいなあと思っているのだが、会場がなかなか確保出来なかったので。まあたまにはこんなのもええかもしれん。参加者数は自分も含めてのべ3名。1名は午前中のみで1名は午前の途中からと午後にかけて。午後に参加された方は熊本から遠路はるばる参加いただいた。

イベント案内ページ

本家東京会場

usptomo.doorkeeper.jp

大阪サテライト会場

atnd.org

福岡サテライト会場

atnd.org

イベント関連ページ

リンク集

第24回◯◯o◯裏番組シェル芸勉強会リンク集 – 上田ブログ

大阪サテライト会場レポート

kunst1080.hatenablog.com

Togetterおまとめ

togetter.com

開始前

今回はすぐに午前の部が始まったので、特にイントロは話さず。一応こんなの準備はしていたのだが使わず。 speakerdeck.com

午前の部

今回午前の部は初心者向けに偽りなし!?な感じだった。前半は上田会長によるawk入門編。manの読み方から基本的な使い方の説明があった。

後半はgreymdさんによる初心者向け問題が展開されていった。
毎日叩ける シェル芸を覚えよう! // Speaker Deck

午前の部にいらっしゃった方はawkやテキスト処理にあまり慣れていらっしゃらないので、私が補足説明をしながらこちらのペースで進めて丁度いい感じだった。余談でデータベースを使う場合にCUIでは下記のような感じで処理出来る、ってのをお話ししたり。CUIとGUIのそれぞれの良さがあるということが面白いと思っていただけたようだ。

$ echo 'SELECT * FROM users;' | mysql 接続先サーバ

お昼は近所の博多ラーメンへ。長いこと行ってなかったので久しぶりに美味しかった。長浜ラーメンはシオカライぜとか、福岡市内で豚骨ラーメン以外はなかなか流行らんのうとか、そもそもラーメン自体酸化した脂肪だから体にワルイやろ!と身も蓋もないこととか。

午後の部

人数は減って私ともう一人の2名でさしの勝負!?の時間となった。いよいよ本番へ突入。ひどい問題という割には?比較的マトモな印象であったような??

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

【問題と解答】第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 

参考にさせていただいたツイート。

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入門での記事を参考にした。

シェルスクリプトマガジン vol.39

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で教えていただいた。

papiro.hatenablog.jp

以上、今回も大変お疲れ様だった。

終了後

午後も参加いただいた方は、熊本から遠路はるばるいらっしゃった。ずいぶん前に福岡の勉強会でお会いしてから大変お久しぶりであった。TwitterでTukubaiコマンドに興味があるのを聞いていたので、どんなコマンドがあるかなど解説したり。

お話を聞いてみると、仕事での開発はC#でやることが多いらしいが、データ処理やお仕事の自動化では、シェルやLispを扱うことが多いらしい。S式が飛び交うAPIをwgetで叩いてるとか面白い。やっぱりシェルやテキスト処理は必要な素養なんだなあと改めて実感した次第。

またデータからコマンド列を作ってシェルにパイプで渡して実行したり、sedの命令やgrepの検索文字列をデータから作って渡す、という考え方も面白いと思っていただけたようだ。これが出来るとメタプログラミング的な要素をCUIで実現出来るので重要だと思うし、今回の問題もそういう意図があった気がしている。

1年くらい前から筋トレを本格的にされていて、漢も惚れそなカッチョイイ体。自分も最近少しながら筋トレ始めてるので、今後も色々参考にさせていただきたい。