日々之迷歩

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

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

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

文化の日ではありますが、シェル芸で頭脳をぶっ叩くのも乙な感じではないでしょうか?世間一般は文化的祝日ですが、福岡サテライト会場に3人が集まりました。遠くは長崎から参加ありがとうございます。福岡サテライト開催も19回目になりました。問題作成解説の上田さん、ありがとうございます。参加者の皆様もお疲れ様でした。

勉強会の情報

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

b.ueda.tech

最近シェル芸界隈で合成文字が流行しておりますが、その影響でイベント通知ページのタイトルが見難い上にググられにくくなっております。

午前の部

午前中は鳥海さんによる文字コードの解説を聞きました。

twitter.com

EUCやShift-JISの成り立ちが面白かったのと、Shift-JISのトリッキーなコードに闇を感じました。 ワンライナーで文字コード表やネストした三項演算子が繰り出される時間でした。 ダメ文字問題なんてあったのを知りました。

次回はようやくUnicodeの世界へ突入らしいですが、文字コードの設計ってすごく大変そうです。

vimに付属しているバイナリダンプやでコードが出来るxxdコマンドですが、 -uオプションで大文字出力が出来るのがあまり知られてないようでした。 xxdのオプションについては、マニュアルで一度確認してみましょう。 読み出しのオフセットや長さ指定、出力の列数、エンディアンの切り替えなどの指定が出来ます。

昼食

参加者みんなで会場近くのパレスチナ料理屋「D-TRANOI」という店へ入ってみました。

tenjinsite.jp

香辛料がすごく効いたサッパリした辛さでした。しかし食べるうちに口がヒリヒリしてきて、かなーり辛いが美味いですね。

戻ってからしばらくしてイントロを話しました。バッチファイルやってみた的な。

speakerdeck.com

午後の部

今回は私も問題にじっくり対峙していた感がありましたが、 福岡サテライトでもサポートや解説はいつもより控えめで大丈夫だった気がします。 参加者の方々がレベルアップしてきたのを感じた日でした。

Q1

モヤモやを除去・・・要するに結合文字を分離しろということでしょうか? 解答例は出題者上田さんの解説を参照いただいて、ここではモヤモヤ文字がどうなっているかを確認してみます。

MacやFreeBSD付属のodコマンドは、マルチバイトに対応しておりで確認に使いやすいと思います。 16進数のコードでd288やd289になっている部分がモヤモヤの部分です。

$ echo 'jus共催 第38回҈҈҉҈҈҉シ҈҉ェ҈҉ル҈҉芸҈҉勉҈҉強҈҉会' | od -t x1c
0000000    6a  75  73  e5  85  b1  e5  82  ac  20  e7  ac  ac  33  38  e5
           j   u   s  共  **  **  催  **  **      第  **  **   3   8  回
0000020    9b  9e  d2  88  d2  88  d2  89  d2  88  d2  88  d2  89  e3  82
          **  **    ҈  **    ҈  **    ҉  **    ҈  **    ҈  **    ҉  **  シ  **
0000040    b7  d2  88  d2  89  e3  82  a7  d2  88  d2  89  e3  83  ab  d2
          **    ҈  **    ҉  **  ェ  **  **    ҈  **    ҉  **  ル  **  **    ҈
0000060    88  d2  89  e8  8a  b8  d2  88  d2  89  e5  8b  89  d2  88  d2
          **    ҉  **  芸  **  **    ҈  **    ҉  **  勉  **  **    ҈  **    ҉
0000100    89  e5  bc  b7  d2  88  d2  89  e4  bc  9a  0a                
          **  強  **  **    ҈  **    ҉  **  会  **  **  \n                
0000114

Q2

ファイルの中に記載された文字だけを抽出する問題。 私の解答はuniq-dオプションで重複チェックを利用したものです。

$ echo 不摂生 | grep -o . | cat - <(cat 仏説摩訶般若波羅蜜多心経 | grep -o . | sort | uniq) | sort | uniq -d
生
不

出題者上田さんの解答はgrep-fオプションを使っていました。 福岡サテライトでは、grep-fオプションについて補足説明をしました。

Q3

スクレイピングで元号リストを取得する必要がある問題。w3mを使って楽をしちゃいました。 福岡サテライト参加者の中から「grep-fオプションを使うといいんじゃ?」という意見が出まして、 ということで下記のようなスッキリとした解答が出せました。

# 何度もアクセスすると迷惑なのでファイルに保存
$ w3m -dump https://edo100.tokyo/edogengolst/ | sed -n /天文/,/明治/p | awk '{print $1}' > gengo

$ cat gengo | grep -f edo
元和
元禄
享保
安政

Q4

bashのブレース展開の問題でした。 {A..Z} {A..Z}{A..Z} {A..Z}{A..Z}{A..Z}と3つのブレース展開を使えば良かったですね。

Q5

ガウス分布の乱数発生は「シェルプログラミング実用テクニック」、 ヒストグラムを描く問題は「シェルスクリプト高速開発入門」にあったのを思い出しました。 どちらも問題出題者上田さんの著書です。

上記著書によると、0から1の範囲の一様乱数を12個ずつ足して6引くと、標準偏差1のガウス分布に近いな乱数が出せるようです。 関連する内容のブログを以前に書いていたので、一応載せておきます。 papiro.hatenablog.jp

Q6

これは文字を一つずつズラしながら表示する処理が必要な問題。 まずファイルの中身から4文字を1文字ズラしながら出力する処理を考えてみます。

$ cat 仏説摩訶般若波羅蜜多心経
観自在菩薩行深般若波羅蜜多時。照見五蘊皆空。度一切苦厄。
舎利子。色不異空。空不異色。色即是空。空即是色。受想行識亦復如是。
(省略)
是無等等呪。能除一切苦。真実不虚故。説般若波羅蜜多呪。
即説呪曰。羯諦羯諦波羅羯諦波羅僧羯諦菩提薩婆訶。般若心経

出題者上田さんの解答はawksubstr()関数を使っておられました。 別の考え方としてフィールド区切り文字を変更するやり方をやってみます。 awkのフィールド区切り文字を空文字で指定すると、 第一フィールド($1)が一文字目、第二フィールド($2)が二文字目を参照します。 つまり1文字ずつフィールド分割して処理が可能になります。 フィールド区切り文字の指定方法には、下記の2通りがあります。

# 1. awkの-Fオプションを使う。
$ cat 仏説摩訶般若波羅蜜多心経 | tr -d '\n' | awk -F '' '{for(i=1;i<=NF-3;i++)print $i$(i+1)$(i+2)$(i+3)}'
観自在菩
自在菩薩
在菩薩行
(省略)
訶。般若
。般若心
般若心経

# 2. 第二引数(つまり入力ファイル名)に var=val の形式を指定すると、変数への代入と解釈される。
awkのフィールド区切り文字は特殊変数FSで指定可能。
$ cat 仏説摩訶般若波羅蜜多心経 | tr -d '\n' | awk '{for(i=1;i<=NF-3;i++)print $i$(i+1)$(i+2)$(i+3)}' FS=

後は2つのファイルについて4文字ずつズラした出力を取り出し、 両方のファイルに含まれるものを選び出します。

Q7

a2 + b2がab + 1で割り切れる正の整数の組合せa,bの生成。 1から1000までの整数を使った解答例がこちらです。 乱数の発生はawksrand()関数とrand()関数を利用しました。

$ yes | awk 'BEGIN{srand()}{print int(1000*rand())+1,int(1000*rand())+1}'
122 849
338 152
654 454
366 786
(省略)

後はawkでa2+b2をab + 1で割った剰余がゼロの時のみ出力するパターンを使います。

$ yes | awk 'BEGIN{srand()}{print int(1000*rand())+1,int(1000*rand())+1}' | awk '!(($1^2+$2^2)%($1*$2+1))'
27 240
30 8
27 3
1 1
3 27
6 216

更に(a2+b2)/(ab+1)の計算も出力して確認します。 ポイントは2つ目のawkfflush()関数を使っている事です。 fflush()関数を使わない場合、バッファリングされてなかなか出力されません。

$ yes | awk 'BEGIN{srand()}{print int(1000*rand())+1,int(1000*rand())+1}' | awk '!(($1^2+$2^2)%($1*$2+1)){print;fflush()}' | awk '{print $1,$2,($1^2+$2^2)/($1*$2+1)}'
125 5 25
8 30 4
27 3 9
64 4 16
8 2 4
112 30 4

(a2+b2)/(ab+1)が正の整数の二乗になっていることの確認までやるには、出題者上田さんの解答例を参照してください。

出力結果を更にパイプで他のコマンドに渡す際ですが、出力のデータ量が少ないとバッファリングされてなかなか出力されない現象が起きる場合があります。出力時に明示的にバッファリングさせない関数やオプションがあるコマンドの例がこちらです。

  • awk: fflush()関数
  • grep: --line-buffered オプション
  • sed: -u, --unbuffered オプション

Q8

前のデータの結果から次を出力し続ける計算。

この問題はawkの出力時に小数点以下の桁を減らさないのがポイントだった。 sprintf()関数を使う方法もあるが、awkには数値出力時のフォーマットを指定するOFMTという特殊変数がある。

参加者の方がツイートされていた例を参考にしてみました。

twitter.com

こんな感じで100万行出力し、重複した数値が無いかを確認します。。

$ yes | awk 'BEGIN{OFMT="%.20f";a=3;x=0.5}{x=a * x * (1-x); print x}'
0.75000000000000000000
0.56250000000000000000
0.73828125000000000000
0.57966613769531250000
0.73095991951413452625
(省略)

# 出力桁数が少ないと重複してしまう
$ yes | awk 'BEGIN{OFMT="%.5f";a=3;x=0.5}{x=a * x * (1-x); print x}' | head -n 1000000 | sort | uniq -d
0.65862
0.65873
0.65880
0.65886
0.65891

# 桁数を増やすと重複しなかった
$ yes | awk 'BEGIN{OFMT="%.20f";a=3;x=0.5}{x=a * x * (1-x); print x}' | head -n 1000000 | sort | uniq -d

これ一体何に使うの?と思ったんですが、カオスの研究で重要らしいです。 連続で重複しない数値をたくさん出す必要があるためらしいです。

bcで計算する方法もありました。

twitter.com

終了後

勉強会で出てきた事例に関連のあるブログ記事を書いてたのを思い出しました。 福岡サテライトでは、休息の後で自作記事を紹介しながら内容の解説を解説させていただきました。

papiro.hatenablog.jp

papiro.hatenablog.jp

全体を通して

前回に続いて、今回も長崎から手練れのシェル芸人の方が遠路はるばる参加されました。 参加者の方がレベルアップされてきたと感じられるのは嬉しいですね。 grep-fオプションについて解説したところ、次の問題で活用出来る!と教えていただけたり。 参加者の増減はあるが、参加者の方へのサポートはそれなりに効果は出てきたかな?と思ったのですが、 いやいや私のサポートというのはおこがましい気がしますね。参加者の方々が頑張られているからでしょう。