日々之迷歩

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

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

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

色々と忙しい状況ではあったりましたが、第25回シェル芸勉強会の福岡サテライト会場を開催出来ました。開始時間が早くなりましたが、会場はベータソフト様会議室を準備していただきました。会場管理の方、ありがとうございます。今回の参加者は9名で、山を越えた県外からの参加者もいらっしゃいました。朝から夕方までかなり喋ったので喉がガラガラになりました。

問題作成と解説の上田さん、ありがとうございます。参加者の方々お疲れ様でした。

勉強会の情報

勉強会主催者の上田さんが公開されているページをご覧ください。 b.ueda.tech

開始前

まずはみんなで自己紹介をした後、下記のスライドでイントロのお話をしました。 先日お仕事用の液晶ディスプレイを新調したばかりですが、1週間後に新機種が発表されて複雑な気持ちが否めない今日この頃です。

午前の部

気を取り直して午前の部を開始しました。今回は初心者の方が多いというのもあり、福岡サテライトは独自に進めることにしました。 Apache HTTP Severのログファイルを扱いながら、awkgrepcutsortuniqなどの使い方を説明し、 簡単な集計の方法へと話を進めるスタイルを試しました。

ネタとしては下記のページを参考にさせていただきました。うまく伝わったでしょうか??

qiita.com

Apacheのログファイルは、出題者上田さんのページで公開されているシェル芸勉強用データを利用させていただきました。

b.ueda.tech

お昼は餃子の王将へ食事に出かけました。大量の餃子もみんなで喰らえば怖くない!!ですよね。

午後の部

今回の問題はパズル系でしたが、難易度は幅広く内容も重要なことが多かった気がします。

福岡サテライト会場でで考えた解答と説明の例を記載いたしました。 問題が完全に解けることよりも解説と考え方の説明を重視しています。 解答例はmacOSで作成、GNU版のコマンドはguniqなど頭にgが付くコマンドになっている場合がございまず。

Q1

digpingなどのコマンド出力を文字列処理。grepのオプションやawktrの使い方を確認出来る問題ですね。

$ dig www.usptomo.com | grep -A1 ';; ANSWER SECTION:' | grep -v ';; ANSWER SECTION:' | awk '{print $NF}'
157.7.203.188

$ ping -c 1 www.usptomo.com | head -n 1 | awk '{print $3}' | tr -dc '0-9.'
157.7.203.188

Q2

awkNRを利用して階段状の出力を作ります。

$ yes ひらけ!ポンキッキ | head -n 9 | awk '{for(i=1;i<NR;i++){printf "#"}printf $0$0;print ""}'
ひらけ!ポンキッキひらけ!ポンキッキ
#ひらけ!ポンキッキひらけ!ポンキッキ
##ひらけ!ポンキッキひらけ!ポンキッキ
###ひらけ!ポンキッキひらけ!ポンキッキ
####ひらけ!ポンキッキひらけ!ポンキッキ
#####ひらけ!ポンキッキひらけ!ポンキッキ
######ひらけ!ポンキッキひらけ!ポンキッキ
#######ひらけ!ポンキッキひらけ!ポンキッキ
########ひらけ!ポンキッキひらけ!ポンキッキ

後は上下ひっくり返し、固定長で文字列を切り出して完成です。

$ yes ひらけ!ポンキッキ | head -n 9 | awk '{for(i=1;i<NR;i++){printf "#"}printf $0$0;print ""}' | tail -r | cut -c 9-17
ひらけ!ポンキッキ
らけ!ポンキッキひ
け!ポンキッキひら
!ポンキッキひらけ
ポンキッキひらけ!
ンキッキひらけ!ポ
キッキひらけ!ポン
ッキひらけ!ポンキ
キひらけ!ポンキッ

Linuxの場合は最後の2つのコマンドを下記のように変更しよう。

  • tail -rtacへ変更
  • cut -c9-17awk {print substr($1,9,9)}へ変更(GNU cutコマンドはマルチバイト非対応)
###Linux用解答###
$ yes ひらけ!ポンキッキ | head -n 9 | awk '{for(i=1;i&lt;NR;i++){printf "#"}printf $0$0;print ""}' | tac | awk '{print substr($1,9,9)}'

Q3

teeを使うことしか思いつきませんでした。@ebanさんの下記の解答例が面白くて、福岡会場で取り上げて説明をさせていただきました。 UNIXの入出力は全てファイルとして扱うことが出来るという事例だと思います。

Q4

ギブアップ!出題者上田さんの解答例を拝見いたしました。

Q5

ちょっと難しく考えすぎたかもしれないのですが、下記の解答例を考えて解説しました。まずはsleepを使ったコマンド列を作ります。 キャリッジリターンを使って改行させない指定がポイントです。

$ yes | head | awk '{for(i=1;i<=NR;i++){printf "*"}print ""}' | sed "s/^/printf '/" | sed "s/$/\\\\r'; sleep 1/"
printf '*\r'; sleep 1
printf '**\r'; sleep 1
printf '***\r'; sleep 1
printf '****\r'; sleep 1
printf '*****\r'; sleep 1
printf '******\r'; sleep 1
printf '*******\r'; sleep 1
printf '********\r'; sleep 1
printf '*********\r'; sleep 1
printf '**********\r'; sleep 1

事前に上記の連続コマンドを作成し、シェルに渡して実行する例として解説した。

$ yes | head | awk '{for(i=1;i<=NR;i++){printf "*"}print ""}' | sed "s/^/printf '/" | sed "s/$/\\\\r'; sleep 1/" | sh
**********

Q6

内容はおそらく16進数ダンプだと思ったので、xxdでデコードしてfileコマンドに渡してデータが何かを確認したのですが謎ですね。

$ echo b730a730eb30b8820a00 | xxd -p -r | file -
/dev/stdin: Non-ISO extended-ASCII text

最後をよく見ると0a00となっています。0aは改行コードの16進ダンプで、改行が最後になってないので不自然だなと思いました。 そこで2文字つまり1バイトずつ入れ替えているのでは?と推測しました。 入れ替え処理後、再度fileコマンドで確認しましたが結局分かりませんでした。 2文字ずつ区切って入れ替える処理や、fileコマンドでの確認について解説をしました。

$ echo b730a730eb30b8820a00 | fold -w 2 | xargs -n 2 | awk '{print $2,$1}' | xargs | tr -d ' ' | xxd -p -r | file -
/dev/stdin: data

Q7

秒毎に繰り返しを実行するやり方を考えてもらいました。dateコマンドはGNU coreutils付属版を利用します。 最初に解説した解答例は下記の通りで、dateコマンドを86400回繰り返すやり方です。

$ seq 0 86399 | while read n; do gdate -d "2016-10-29 00:00:00 $n sec" '+%s'; done
$ for n in {0..86399}; do gdate -d "2016-10-29 00:00:00 $n sec" '+%s'; done
$ seq 0 86399 | xargs -I@ gdate -d '2016-10-29 00:00:00 @ sec' '+%s'
$ seq 0 86399 | sed "s/^/gdate -d '2016-10-29 00:00:00 /" | sed "s/$/ sec' '+%s'/" | sh

次にdateコマンドのフィルタモードで高速処理する方法を説明しました。

$ seq 0 86399 | sed -e "s/^/2016-10-29 00:00:00 /" -e "s/$/ sec/" | gdate -f - '+%s'

素数のみを出力するのは、いつものfactorコマンドを使ったシェル芸的方法です。 最後にUNIX時間から元に戻すのを忘れていました。

$ seq 0 86399 | sed -e "s/^/2016-10-29 00:00:00 /" -e "s/$/ sec/" | gdate -f - '+%s' | gfactor | awk 'NF==2{print $2}'

Q8

福岡サテライトでは、難易度と時間の都合上飛ばしました。 勉強会終了後に考えた解答がこちらです。出題者上田さんの解答例を参考にさせていただき、後半の行列入替をawkで頑張っただけです。

$ seq 1 50 | awk '{a=sin($1/3) * 10 + 10;for(i=0;i<a;i++)printf "-";printf "+";for(i=a;i<20;i++)printf "-";print ""}' | awk '{for(i=1;i<=NF;i++)a[NR,i]=$i}END{for(j=1;j<=NF;j++){for(i=1;i<=NR;i++){printf a[i,j]}print ""}}' FS= | tail -r | tr '+-' '* '
                                                  
   ***                **                 **       
  *                  *  *               *  *      
      *                                           
 *                  *    *             *    *     
                                                  
       *                  *           *           
*                  *                         *    
                                                  
        *                            *            
                  *        *                  *   
                                                  
         *                                        
                 *          *       *          *  
                                                  
                                                  
          *     *            *     *            * 
                                                  
           *                  *   *               
               *                                 *
            ***                ***                
                                                  

行列入替は下記の記事で書いたものを流用しました。

http://papiro.hatenablog.jp/entry/2016/09/24/164244papiro.hatenablog.jp

終了後

もう一度全体を通して復習をしながら説明しました。 福岡の解答例はATNDイベントページのコメントに貼り付けて共有すればいいね!ということに気がつきました。

今回も解説重視でひたすらしゃべった感じでした。わかりやすい説明はどうすればいいか、毎回悩みながら話しています。 シェルやコマンドに親しむ楽しさ、コンピュータに頑張ってもらうためのヒントなどが伝わってるといいなあと思っていますがどうでしょうか? 今回は喉がかなりガラガラになりました。