日々之迷歩

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

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

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

梅雨なのにあんまり雨が降らずこれはアカンと思う今日この頃です。通例では偶数月がシェル芸勉強会の月だが、今回は7月1日での開催となりました。今回も株式会社エスコ様(旧ベータソフト様)の会議室を利用させていただきました。会議室管理者の方ありがとうございます。 問題作成と解説の上田さん、ありがとうございます。参加者の皆様もお疲れ様でした。

会場への移動中は暑かったですね!今回福岡サテライトは5人の参加者が集いました。 この暑い中会場へ移動の移動お疲れ様でした。初参加の方もいらっしゃいました。

勉強会の情報

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

b.ueda.tech

午前の部

強烈eval系ワンライナーで有名な鳥海さんによるPerl入門でした。ちなみにPerl歴0年だとのことです。

ワンライナーが書きやすい機能に改めて気が付いた内容でした。

  • ダイヤモンド演算子
  • 豊富な特殊変数
  • -naeなどの起動オプション

そういえば豊富な特殊変数については、こんなことを思い出しました。Rubyにも同様な機能がありますね。

use English宣言をすると、特殊変数に英語名が利用可能です。

$ perl -e '$"=":";@a=(1,2,3);print "@a"'
1:2:3

$ perl -e 'use English;$LIST_SEPARATOR=":";@a=(1,2,3);print "@a"'
1:2:3

昼食

お昼は定番となった餃子の王将でした。開発環境の統一性に関する政治力の考察!?などの話題が上がりました。

午後の部

午後の部開始前にイントロの話をしました。富士山撮影に失敗したとです・・・ そしてlessコマンド終了時にショボーンとなる人たちの救済が必要なのを感じた次第です。

speakerdeck.com

そしていよいよ午後の部開始です。今回は地獄の三部構成とのことです、楽しみですね。

福岡サテライトで考えて解説した内容は下記の通りです。

Q1

問題の意図としてはjoinコマンドを使うんだろうと思いつつ、awkの連想配列を使った解答例を考えました。 1列目と2列目をセットでキーにしています。

$ cat kadai1 kadai2 | awk '{a[$1" "$2]+=$3}END{for(v in a)print v,a[v]}' | sort -k1,1n
004 今泉 22
001 山田 40
005 鳥海 88
002 出川 30
003 上田 15

もう一つTukubaiのsm2コマンドを使った例です。 自然に2列をキーにする場合は、awkよりも自然に記載出来て便利ですね。

$ cat kadai1 kadai2 | sort -k1,1n | sm2 1 2 3 3

Q2

ようやくjoinコマンドを発動しました。-aオプションを使った補完の出番です。 途中まで出来たけどここから?と思っていましたが、あとはawkでゴニョゴニョやればよかったですね。

$ cat attend6 | tr ',' '\n' | sort | sed 's/$/ 出/' | join -a 1 attend -
001 山田 出出欠出出 出
002 出川 出出欠欠欠
003 上田 出出出出出 出
004 今泉 出出出出出
005 鳥海 欠出欠出欠 出

勉強会後、このブログを書いている時に考えた解答例がこちらです。 GNU coreutils付属のjoinコマンドを使う想定で、-oオプションと-eオプションを追加しました。

$ cat attend6 | tr ',' '\n' | sort | sed 's/$/ 出/' | join -a 1 -o 1.1 1.2 1.3 2.2 -e 欠 attend - | sed 's/ \(.\)$/\1/'
001 山田 出出欠出出出
002 出川 出出欠欠欠欠
003 上田 出出出出出出
004 今泉 出出出出出欠
005 鳥海 欠出欠出欠出

macOSやFreeBSD付属のjoinコマンドを使う場合はこちらです。 -oオプションでの列指定がコンマ区切りになるのに注意しましょう。

$ cat attend6 | tr ',' '\n' | sort | sed 's/$/ 出/' | join -a 1 -o 1.1,1.2,1.3,2.2 -e 欠 attend - | sed 's/ \(.\)$/\1/'
001 山田 出出欠出出出
002 出川 出出欠欠欠欠
003 上田 出出出出出出
004 今泉 出出出出出欠
005 鳥海 欠出欠出欠出

Q3

色々ゴニョゴニョやってみたが上手く出来ませんでした。

Q4.1

まずは各行の文字列長を1列目に付けてみます。

$ echo -1 4 5 2 42 421 44 311 -9 -11 | tr ' ' '\n' | sort -n | awk '{print length,$0}' 
3 -11
2 -9
2 -1
1 2
1 4
1 5
2 42
2 44
3 311
3 421

更に数値がマイナスの行の文字列長にマイナスを付けます。

$ echo -1 4 5 2 42 421 44 311 -9 -11 | tr ' ' '\n' | sort -n | awk '{print length,$0}' | sed '/-/s/^/-/' 
-3 -11
-2 -9
-2 -1
1 2
1 4
1 5
2 42
2 44
3 311
3 421

後は1列目をキーにして横並びの処理をすれば完成です。

$ echo -1 4 5 2 42 421 44 311 -9 -11 | tr ' ' '\n' | sort -n | awk '{print length,$0}' | sed '/-/s/^/-/' | awk '{a[$1]=a[$1]" "$2}END{for(v in a)print v,a[v]}' | sort -k1,1n | awk '{$1="";print}'
 -11
 -9 -1
 2 4 5
 42 44
 311 421

Q4.2

この問題はやる時間が取れませんでした。 sortコマンドの-gオプションを使えば+符号がついても数値による並べ替えが出来ることを知りました。

$ echo 4 +5 -1 -2 | tr ' ' '\n' | sort -n
-2
-1
+5
4

$ echo 4 +5 -1 -2 | tr ' ' '\n' | sort -g
-2
-1
4
+5

Q5

データの回転をするには天地返し+行列変換をすればいいのでtac | rs -Tの技が使えないか?と考えました。 macOSやFreeBSDの場合はtacコマンドの代わりにtail -rを使います。(GNU coreutilsのtacを入れる手もあり)

$ seq 1 9 | xargs -n 3
1 2 3
4 5 6
7 8 9

$ seq 1 9 | xargs -n 3 | rs -T | tac
3  6  9
2  5  8
1  4  7

相手のデータは三角なので四角にすればいいのでは?と考えている間にタイムアップでした。

Q6

まずは問題のデータと全ての素数を混ぜ合わせて並べ換えます。ここまでは出題者上田さんの解答とほぼ同じでした。

$ seq 1 100 | gfactor | awk 'NF==2{print $2}' | cat <(cat prime | tr ' ' '\n') - | sort -n
2
2
3
3
(出力は省略)
89
89
97
97

ここから重複の数を数えます。

$ seq 1 100 | factor | awk 'NF==2{print $2}' | cat <(cat prime | tr ' ' '\n') - | sort | uniq -c | sort -s -k2,2n
      2 2
      2 3
(出力は省略)
      1 23
      1 29
(出力は省略)
      2 89
      2 97

後は1列目が1の時に改行して完成です。

$ seq 1 100 | factor | awk 'NF==2{print $2}' | cat <(cat prime | tr ' ' '\n') - | sort | uniq -c | sort -s -k2,2n | awk '$1==2{printf $2" "}$1==1{print ""}' | awk NF
2 3 5 7 11 13 17 19 
31 37 41 43 47 53 59 
67 71 73 79 83 89 97 

Q7

まずはnkfコマンドに実体参照の復号機能があることを解説しました。 後は頑張ってタグを取れば良さそうですがタイムアップでした。

$ echo 'a&#160;b' | nkf --numchar-input
a b

後はテキストブラウザのw3mを使う解答例もあります。

$ w3m -dump nyaan.html 
                                                        
 "m   mmm                                     "m        
 m" "" m""        m m                         m"        
 #              mm#m"#"m     ""      ""      m#m        
 # m #            "m mm"                    m"  #   m   
 "#   """"         #                       m"   "mm"    
                                                        

Q8

考えているうちに以前にやっていた「バナー芸人衆」という遊びを思い出しました。

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

この記事をヒントに考えていたら、「行列変換して空白だけの行を削除」すればいいやん!と閃いた解答例がこちらです。 まずは空白を-に置換して行列変換します。

$ cat shellgei | tr ' ' '-' | sed 's/./& /g;s/ $//' | rs -T
-  -  -  -  -  -  -  -
-  -  -  -  -  -  -  -
-  -  m  -  -  "  -  -
-  "  m  -  -  m  -  -
-  "  -  "  -  m  -  -
-  m  -  -  -  "  -  -
-  -  -  -  -  "  -  -
-  -  -  -  m  -  -  -
-  -  -  -  "  -  -  -
-  -  -  m  -  -  -  -
(出力は省略)
-  -  -  "  -  -  -  -
-  -  -  "  -  -  -  -
-  -  m  m  #  #  -  -
-  -  "  -  -  -  -  -
-  #  -  -  -  -  -  -
-  -  -  -  -  -  -  -
-  -  -  -  -  -  -  -
-  -  -  -  -  -  -  -
-  -  -  -  -  -  -  -
-  -  -  -  -  -  -  -

次のgrep -v '^[- ]*$'で空白と-だけの行を(つまり元は空白だけの列)削除します。 後は2つめのrs -Tで元に戻し、-を空白に戻して完成です。

$ cat shellgei | tr ' ' '-' | sed 's/./& /g;s/ $//' | rs -T | grep -v '^[- ]*$' | rs -T | tr -d ' ' | tr '-' ' '
                          m               
 ""m             m "m      #    # #      #
mm                # #      #mmm"""     m" 
  "    m" mmm""   # #   # m"   #   mm""m  
     m"     #mm  m" # m" "    #        #  
"mm""    """"  "m"  #"      m"         #  
                                          
                                          

午後の部終了後

スライドまで準備する時間がなかったので、メモを元に話をしました。

バナー芸人衆

Q8を解くのにヒントになったブログ記事についてちらっと解説しました。端末遊びで頭を鍛えましょう。

真・マイナンバーシェル芸

xargsの話題が出たので、-Pオプションで並列化が出来るという例をちらっと話しました。

papiro.hatenablog.jp

X.509デジタル証明書の話題

先日自前で運用しているCAを使ってSSL/TLS用サーバ証明書の発行をしていたのですが、うっかり不正の日付で期限を指定した証明書を発行してしまったのでした。

Google Chromeが証明書で発行先のサーバ情報を参照する際に、CNではなくてSubjectAltNameをチェックするというように仕様が変わっていました。エラーが出てありゃ困ったぞ?という経験をしました。

では改めてX.509デジタル証明書のデータを見てみようということで話をしました。例としてTwitterが使っているSSLサーバ証明書を使いました。デジタル証明書のDER形式のバイナリデータを復元するワンライナーがこちらです。

バイナリデータの可視化はodコマンドを使っているオプションで-t x1cと指定すると、1バイトずつ16進数とASCII文字で出力してくれるので便利です。

$ openssl s_client -connect twitter.com:443 < /dev/null 2> /dev/null | sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' | sed '1d;$d' | base64 --decode | od -t x1c | head
0000000    30  82  06  89  30  82  05  71  a0  03  02  01  02  02  10  6f
           0 202 006 211   0 202 005   q 240 003 002 001 002 002 020   o
0000020    d0  1d  01  74  61  47  18  6f  80  92  a0  39  f3  b3  a8  30
         320 035 001   t   a   G 030   o 200 222 240   9 363 263 250   0
0000040    0d  06  09  2a  86  48  86  f7  0d  01  01  0b  05  00  30  7e
          \r 006  \t   * 206   H 206 367  \r 001 001  \v 005  \0   0   ~
0000060    31  0b  30  09  06  03  55  04  06  13  02  55  53  31  1d  30
           1  \v   0  \t 006 003   U 004 006 023 002   U   S   1 035   0
0000100    1b  06  03  55  04  0a  13  14  53  79  6d  61  6e  74  65  63
         033 006 003   U 004  \n 023 024   S   y   m   a   n   t   e   c

X.509デジタル証明書はASN.1記法に基づいたデータ構造になっており、OpenSSLのasn1parseサブコマンドで可視化出来ます。

$ openssl s_client -connect twitter.com:443 < /dev/null 2> /dev/null | sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' | sed '1d;$d' | base64 --decode | openssl asn1parse -i -inform DER
    0:d=0  hl=4 l=1673 cons: SEQUENCE          
    4:d=1  hl=4 l=1393 cons:  SEQUENCE          
(出力は省略)
  174:d=2  hl=2 l=  30 cons:   SEQUENCE          
  176:d=3  hl=2 l=  13 prim:    UTCTIME           :150902000000Z
  191:d=3  hl=2 l=  13 prim:    UTCTIME           :170730235959Z
  206:d=2  hl=3 l= 137 cons:   SEQUENCE          
(出力は省略)
  326:d=4  hl=2 l=  18 cons:     SEQUENCE          
  328:d=5  hl=2 l=   3 prim:      OBJECT            :commonName
  333:d=5  hl=2 l=  11 prim:      UTF8STRING        :twitter.com
(出力は省略)
  650:d=5  hl=2 l=   3 prim:      OBJECT            :X509v3 Subject Alternative Name
  655:d=5  hl=2 l=  32 prim:      OCTET STRING      [HEX DUMP]:301E820B747769747465722E636F6D820F7777772E747769747465722E636F6D

UTCTIMEの2つ目が期限日です。YYMMDDHHMMSS形式にZがついた文字列になっています。最後に付いたZがUTC時間の意味です。ここで重要なのは、期限日は文字列であるということです。つまり不正な日付も入れることが出来るのです。

UTF8STRINGの部分がCommonName(CN)と呼ばれる部分で、サーバ証明書の場合は通常ホスト名を指定します。しかし最近のGoogle Chromeは、証明書のホスト名を次のOBJECT :X509v3 Subject Alternative Nameで参照するようになりました。実際のデータは16進数のバイナリデータとなっています。ホスト名が2つ登録されているが見えるでしょうか?

$ echo 301E820B747769747465722E636F6D820F7777772E747769747465722E636F6D | xxd -p -r | od -t x1c
0000000    30  1e  82  0b  74  77  69  74  74  65  72  2e  63  6f  6d  82
           0 036 202  \v   t   w   i   t   t   e   r   .   c   o   m 202
0000020    0f  77  77  77  2e  74  77  69  74  74  65  72  2e  63  6f  6d
         017   w   w   w   .   t   w   i   t   t   e   r   .   c   o   m
0000040

openssl x509でSubject Alternative Nameを可視化したのがこちら。2つのホスト名が登録されているのがわかります。

$ openssl s_client -connect twitter.com:443 < /dev/null 2> /dev/null | sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' | sed '1d;$d' | base64 --decode | openssl x509 -inform DER -noout -text | grep -A1 'Subject Alternative Name'
            X509v3 Subject Alternative Name: 
                DNS:twitter.com, DNS:www.twitter.com

バイナリデータを閲覧するコマンドについて補足をしておきました。macOSやFreeBSDのodコマンドはマルチバイト文字に対応しているので便利ですね。

$ echo 'The シェル芸 Power' | od -t x1c
0000000    54  68  65  20  e3  82  b7  e3  82  a7  e3  83  ab  e8  8a  b8
           T   h   e      シ  **  **  ェ  **  **  ル  **  **  芸  **  **
0000020    20  50  6f  77  65  72  0a                                    
               P   o   w   e   r  \n                                    
0000027

$ echo -n '🍣食いねえ' | od -t x1c
0000000    f0  9f  8d  a3  e9  a3  9f  e3  81  84  e3  81  ad  e3  81  88
           🍣  **  **  **  食  **  **  い  **  **  ね  **  **  え  **  **
0000020

またvimに付属のxxdコマンドについても説明しました。-p-p -rオプションを使った16進数への符号化と復号化、-bオプションでビット列(2進数)への符号化など。

$ echo 'The シェル芸 Power' | xxd -p
54686520e382b7e382a7e383abe88ab820506f7765720a

$ echo 'The シェル芸 Power' | xxd -p | xxd -p -r
The シェル芸 Power

$ echo 'The シェル芸 Power' | xxd -b
00000000: 01010100 01101000 01100101 00100000 11100011 10000010  The ..
00000006: 10110111 11100011 10000010 10100111 11100011 10000011  ......
0000000c: 10101011 11101000 10001010 10111000 00100000 01010000  .... P
00000012: 01101111 01110111 01100101 01110010 00001010           ower.

その後雑談を兼ねたいろんなお話が出てきました。皆さんいろんな環境でいろんな苦労をされていらっしゃいますね。

シェル芸勉強会って「データに着目する」視点が身に付くんだなあと思いました。シェル芸はデータストリームな処理なので、必然的にデータに着目するからかもしれないですね。

今回は初参加の方もいらっしゃったので、もう少し自己紹介などの時間を取ればよかったと思いました。自分なりに解説やサポート、話題提供を頑張ったつもりですが、もう少し肩の力を抜いていいかもしれないです。