日々之迷歩

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

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

「第37回シェル芸勉強会:福岡サテライト」レポート

シェル芸勉強会福岡サテライトの開催が、昨年の冬以降途絶えておりました。時間が取れなかったり、前回は大雨の影響で中止したりでした。 株式会社レスコ様の会議室を貸していただき、久しぶりに開催出来ました。いつもながら会議室管理者の方、ありがとうございます。今回は述べ8人の参加者が集いました。そのうち新規の方が3人、2人は遠方からの参加でした。 勉強会主催者の上田さん、問題作成と解説ありがとうございました。

勉強会の情報

勉強会主催者の上田さんが公開されているリンク集をご覧ください。

b.ueda.tech

午前の部

最初に話したイントロです。

speakerdeck.com

午前中は鳥海さんによる文字コードの話を聴く予定でしたが、方針を変えて主に初参加者向けに補足説明をすることにしました。勉強会の趣旨や内容、よく使うコマンドやその使い方や調べ方について話をしました。catのオプション、sort、uniq、comm、paste、ls、findとxargsの合わせ技、bc、awkなども一通り説明。

その後以前実施した「初心者向けシェル芸勉強会」で解いた問題をやってみることにしました。

シェル芸初心者向け勉強会

papiro.hatenablog.jp

シェル芸勉強会過去問第2回(問題と解答)

シェル芸勉強会過去問第2回(問題と解答) · GitHub

解いてもらって解説をしたのは下記の問題です。

問題2: 計算

以下のファイル中の数字を全部足し算してください。

$ cat Q2/num
1
2 3 4
5 6
7 8 9 10

文字列処理で数式を作ってbcへ突っ込むという解答を解説しました。awk NFは最後に改行を入れるためのおまじないみたいなものです。

$ cat Q2/num | tr '\n' ' ' | tr ' ' + | sed 's/.$//' | awk NF | bc
55

昼食

前回に新規開拓した中華料理店へ行きました。何故か福岡県や福岡市の良さやアレな事が話題になったりしました。福岡県と福岡市の相性ってアレだよねとか(自粛)。

午後の部

いよいよ問題にトライの時間。今回は難しくもあるがくだらない?問題とのことです、、、??

要するに「シェル芸bot映え」するような出力を目指す問題でした。シェル芸botについては、Twitterを使ってる人はフォローして使ってみてください。作者の方が手動でフォロー返しされたら利用可能になるので、しばしお待ちを。

今回はいつも以上に福岡サテライトなりのペースで、というより私なりのペースでフォローや解説を重視しながら進行した。

Q1

FizzBuzzの出力をアニメーション化する問題。福岡ではとりあえずFizzBuzzの問題を解くことにしました。普通はループで剰余計算の結果でif文書きますが、sedで文字列処理を使った解答を紹介しました。GNU sedの0~3の様な行指定がポイントです。最後のxargsは縦に長くなるのを嫌ったためです。。

$ seq 30 | sed '0~3s/$/Fizz/;0~5s/$/Buzz/;' | sed 's/^[0-9][0-9]*\([^0-9][0-9]*\)/\1/' | xargs
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz

Q2

福岡サテライトでは下記の出力を出すことまでを目標にしました。

   /\
  /  \
 /    \
/      \

出題者上田さんの解答前半部を補足説明しました。GNU sedのラベルを使った繰り返し処理という初心者殺しな内容でしたが、とりあえず理解してもらえた、、でしょうか?。繰り返しのbコマンドは無限にくり返します。

$ echo '   /\' | sed ':a p;s@ /@/  @;b a'
   /\
  /  \
 /    \
/      \
/      \
..(無限君)...

繰り返しコマンドでbの代わりにtを使うと、直前のsコマンドが失敗するまで繰り返します。

$ echo '   /\' | sed ':a p;s@ /@/  @;t a'
   /\
  /  \
 /    \
/      \
/      \

Q3

問題簡略化のため、3桁の数字で各桁を足すと15になる数字の列挙をする事にしました。bashのブレース展開、sedで空白を入れる、awkで処理という流れです。

$ echo {0..9}{0..9}{0..9} | tr ' ' '\n' | sed 's/./& /g' | awk '$1+$2+$3==15' | tr -d ' ' | xargs
069 078 087 096 159 168 177 186 195 249 258 267 276 285 294 339 348 357 366 375 384 393 429 438 447 456 465 474 483 492 519 528 537 546 555 564 573 582 591 609 618 627 636 645 654 663 672 681 690 708 717 726 735 744 753 762 771 780 807 816 825 834 843 852 861 870 906 915 924 933 942 951 960

awkの-F ''オプションで空文字を区切り文字に指定すると、$1から順に1文字ずつ扱えて便利ですね。

$ echo {0..9}{0..9}{0..9} | tr ' ' '\n' | awk -F '' '$1+$2+$3==15' | xargs
069 078 087 096 159 168 177 186 195 249 258 267 276 285 294 339 348 357 366 375 384 393 429 438 447 456 465 474 483 492 519 528 537 546 555 564 573 582 591 609 618 627 636 645 654 663 672 681 690 708 717 726 735 744 753 762 771 780 807 816 825 834 843 852 861 870 906 915 924 933 942 951 960

Q4

bash4の機能でUnicodeのコードポイントを指定した文字表示が可能という解説をしました。macのログインシェルはbash3なので出来ません。Macの人はhomebrewで別途bash4をインストールしてもらいました。

$ echo -e \\U1F000
🀀

空文字の列とブレース展開して、同じ文字を繰り返し表示してみるところまでやってみました。

$ echo -e \\U1F00{0,1,2,3,6}{,,,}
🀀 🀀 🀀 🀀 🀁 🀁 🀁 🀁 🀂 🀂 🀂 🀂 🀃 🀃 🀃 🀃 🀆 🀆 🀆 🀆

Q5

出題者上田さんの解答例について前半部を解説しました。「山田」という漢字を下記の様にバナー化する出力を考えます。

__@__
__@__
__@__
@_@_@
@@@@@
_____
@@@@@
@_@_@
@@@@@
@_@_@
@@@@@

上記のバナーを1行ずつ確認すると、下記の3パターンになる事が分かります。

@@@@@ (5列とも埋まる)
__@__ (3列目だけ埋まる) 
@_@_@ (1列目、3列目、5列目が埋まる)
_____ (全て埋まらない)

上記の4パターンを1列に並べて、バナー化した「山田」が出るようにawkで順番に指定して出力します。1行目から3行めは3列目だけ埋まるパターン、4列目は1列目、3列目、5列目が埋まるパターンという具合です。

$ echo '@@@@@ __@__ @_@_@ _____' | awk '{print $2,$2,$2,$3,$1,$4,$1,$3,$1,$3,$1}' | xargs -n1
__@__
__@__
__@__
@_@_@
@@@@@
_____
@@@@@
@_@_@
@@@@@
@_@_@
@@@@@

Q6

時間がなかったため飛ばしました。

Q7

まずはfiglettoiletコマンドについて解説しました。インストール出来る人はインストールしてもらいました。次にmecabを使ってカタカナに変換しました。awkで-F , '{print $NF}'と指定すると、カンマ区切りで最後の列のみを表示することを利用します。

$ echo とじ丼 | mecab -E '' | awk -F , '{print $NF}' 
トジ
ドンブリ

あとは改行を外してtoiletコマンドへ入力します。

$ echo とじ丼 | mecab -E '' | awk -F , '{print $NF}' | tr -d '\n' | toilet
  m                           m                                m m    
   #             ""m  # #      #   # #      mm          m    mm#m"    
   #mm          mm             #mm            "    #     """"   #     
   #  ""m         "    m"      #  ""m             #            #      
   #                 m"        #                m"           m"       
   #            "mm""          #           "mm""          mm"         
                                                                      
                                                                      
       m                                                              
  "m    #                                                             
   #    #                                                             
   #    #                                                             
       #                                                              
     m"                                                               

出題者上田さんの解答では、漢字を平仮名にするのにkakasiを使っていらっしゃいました。こちらの方が簡単だったかも。そういえばkakasiを使った事があんまりなかったですね。

Q8

簡略化のため下記の様な出力をする事にしました。1行目を左右逆にして2行目に出力します。

鉄皿鶏のチリソース定食
食定スーソリチの鶏皿鉄

まずは左右逆にするrevコマンドを解説しました。

$ echo 鉄皿鶏のチリソース定食 | rev
食定スーソリチの鶏皿鉄

次にmoreutilsに含まれるpeeコマンドを解説しました。上記のrevコマンドを利用して下記の通りです。

$ echo 鉄皿鶏のチリソース定食 | pee cat rev
鉄皿鶏のチリソース定食
食定スーソリチの鶏皿鉄

peeコマンドの代わりに、teeコマンドとプロセス置換の組み合わせでも可能ですね。

$ echo 鉄皿鶏のチリソース定食 | tee >(cat) >(rev) > /dev/null
鉄皿鶏のチリソース定食
食定スーソリチの鶏皿鉄

終了後

福岡サテライトでは、シェル芸勉強会終了後、午前に引き続き初心者向けの話と「初心者向けシェル芸勉強会」でやったウォーミングアップ問題を解いていただいた。

色々と話をさせていただいたのだが、ここではコマンドライン自体もテキストデータだと思うと面白いよという話題について。数字の列挙から、日付を列挙するコマンド列をテキスト処理で作成します。

$ seq 0 2 | sed 's/^/date -d "/' | sed 's/$/ day"/'
date -d "0 day"
date -d "1 day"
date -d "2 day"

作成したコマンド列をパイプでbashへ渡して実行します。繰り返し処理が無くてもコマンドを順次実行しますね。

$ seq 0 2 | sed 's/^/date -d "/' | sed 's/$/ day"/' | bash
2018年  9月  4日 火曜日 23:06:42 JST
2018年  9月  5日 水曜日 23:06:42 JST
2018年  9月  6日 木曜日 23:06:42 JST

最後に下記の解いてもらいました。

問題: 下記の日付一覧から、月毎の日の数を数えてください

$ cat date1
20150101
20150121
20150201
20150202
20150203
20150310

ポイントは年月までの左から6文字だけを切り出す事ですね。6文字を切り出すにはcut -c1-6awk {print substr($0,1,6)}という意見が出ました。他にはgrepを使った下記も補足しておきました。

$ cat data1 | grep -o '^......'
201501
201501
201502
201502
201502
201503

後はuniq -cを使って集計です。

$ cat data1 | grep -o '^......' | uniq -c
      2 201501
      3 201502
      1 201503

更にsort -nrまでつなげて多い順に並べるのは、良く使うイデェオムということで補足しておきました。

$ cat data1 | grep -o '^......' | uniq -c | sort -nr
      3 201502
      2 201501
      1 201503

全体を通して

今回は2名の方が遠方からいらっしゃいました。プログラミングの勉強をしていて、興味本位で参加してみたという方も。参加者の間口が少しでも広がるのは良い事だと思いました。

とりあえず福岡サテライトは私のアドリブでペース配分してみようと思います。後は参加者の反応をもっと見たり、時間をとる必要もありそうです。

終了後の後片付けで頼まれていた外の張り紙を外し忘れるミスをやってしまい、会場管理の方にご迷惑をおかけしてしまいました。次回からは張り紙外しを忘れないようにせねば。