2021-04-24(土)「第53回シェル芸勉強会」が開催されたので、久しぶりに参加レポートを書いた。コロナ禍の影響で、シェル芸勉強会の福岡サテライト会場は長いこと開催していない。前回は「第45回シェル芸勉強会」2019年12月末だったようだ。勉強会参加の記事も長らく書いて無かった。久しぶりの投稿である。
シェル芸勉強会はここ1年くらいオンライン参加の形態になっている。上田会長が自宅からYoutube配信されるのを見聞きしながら、Twitterで問題を見たり解答したりするようなやり方。最近では雑談やツッコミなどの目的でDiscodeも併用もしている。二次会もオンライン飲み会になっている
勉強会募集サイト
勉強会開催アナウンス
勉強会開催報告など
シェル芸勉強会リンク集
jus共催 第53回シェル芸勉強会リンク集 | 上田隆一の仕事とか
Youtubeライブ配信の録画
Togetterまとめ
参加報告記事など
勉強会の内容について
毎度のことながら、多忙の中問題作成と配信を行われている上田会長には頭が下がる。 ちなみにYoutube配信する環境は、パソコン3台使ってこんな感じのようだ。
twitter.comセッティング完了 #シェル芸 pic.twitter.com/ihyNpQX4hO
— 上田 隆一 (@ryuichiueda) 2021年4月24日
今回の問題は、数学的な問題をAWKでゴリ押しする系。問題と解答はこちら。
問題と解答
【問題と解答】jus共催 第53回シェル芸が好きです。でもゾウさんのほうがもっと好きですシェル芸勉強会 | 上田隆一の仕事とか
私なりの解答例を以下に掲載させていただく。
Q1
1から100万までがランダムに並んだデータで、5列先の数を足して2列目に出力する問題。
$ seq 1000000 | shuf > a $ head a 742505 19482 511202 49851 406516 617563 227367 821010 753384 906981
paste
コマンドを使った解答がこちら。tail
コマンドで6行目以降のデータを作って、横に並べて足し算すれば良い。
$ paste a <(tail -n +6 a) 742505 617563 19482 227367 511202 821010 49851 753384 406516 906981 617563 115432 227367 45000 821010 922566 753384 11506 906981 10750 (以下省略)
あとはデータが2列並んだ行のみ、横に並んだ2つを足し算した結果を2列目に表示すれば良い。
$ paste a <(tail -n +6 a) | awk 'NF==2{print $1,$1+$2}' > b $ head b 742505 1360068 <- 742505 + 617563 19482 246849 <- 19482 + 227367 511202 1332212 49851 803235 406516 1313497 617563 732995 227367 272367 821010 1743576 753384 764890 906981 917731 (以下省略)
twitter.com#シェル芸 Q1間違えてた。tail -n +6だった。
— ぱぴろんちゃん🥺 (@papiron) 2021年4月24日
seq 1000000 | shuf > a; paste a <(tail -n +6 a) | awk 'NF==2{print $1,$1+$2}'
AWK単体での別解はこちら。各行のデータを配列に保存し、6行目以降から現在行のデータと5つ前のデータを足し算する。
$ awk '{a[NR]=$0}NR>=6{print a[NR-5],$0+a[NR-5]}' a > b
Q2
数値を約分する問題キタコレ、、、最初は素因数分解したのを縦に並べてどうにか出来ないか?と考えたがダメだった。
$ cat frac | factor | rs -T | sed 1d 2 3 3 3 3 7 3 13 5 633543227197 347 54601 633543227197
会長の解答例では、各素因数の数を計算し、分子の数から分母の数を引き算していた。なるほど。
便利な道具を使う解答例として、Rubyの有理数を扱う機能を使う例が出ていた。RubyのPEPL環境irb
で試してみる。
$ irb irb(main):001:0> 3240933263267302464930/518871903074343r 【最後にrを付ける】 => (568396410/91) 【約分された結果が表示される】 irb(main):002:0> Rational(3240933263267302464930,518871903074343) => (568396410/91)
解答例では末尾にr
を記載したやり方が出ていたが、Rationalクラスを明示的に書いた解答はこうなるかな。
$ cat frac | awk '{printf "puts Rational(%s, %s)\n",$1,$2}' | ruby | tr / ' ' 568396410 91
twitter.com#シェル芸 Q2の結論、有理数が扱えるRuby便利
— ぱぴろんちゃん🥺 (@papiron) 2021年4月24日
RubyのRationalクラスのマニュアルはこちら docs.ruby-lang.org
Q3
ややこしい四捨五入をする問題。Q2でRubyが便利だったので、こちらもRubyで楽をする解答を作った。
AWKでRubyのコードを作成。
$ cat nums | awk '{print "puts "$0".round("length($2)-1")"}' FS=. puts -0.327.round(2) puts 2.33333.round(4) puts 4.0000000000999995.round(15)
あとはRubyにコードを実行させて完成。
$ cat nums | awk '{print "puts "$0".round("length($2)-1")"}' FS=. | ruby -0.33 2.3333 4.0000000001
Q4
連続して処理を続けて、数が収束するのを確認する問題。一旦処理を行う関数を作成し、関数をパイプで数珠つなぎ実行するというやり方で考えた。 出力を標準エラー出力に出せば、パイプで連続接続しても確認出来るというアイデアは使わせていただいた。
$ f(){ awk '{print > "/dev/stderr"; printf "%04d\n",$0}' | grep -o . | pee 'sort -r' 'sort' | tr -d '\n' | sed 's/....$/-&/' | awk NF | bc; }; eval $(yes f | head | xargs | tr ' ' '|' | sed 's/^/echo 9467|/') |& xargs 9467 5085 7992 7173 6354 3087 8352 6174 6174 6174 6174
会長の上田さんが作成された、コマンドをパイプで数珠つなぎして実行するjuz
というコマンドがあるのを思い出し、使ってみようとしたが動かず。数珠つなぎするのが外部コマンドはなく関数なのが原因だろう。
$ f(){ awk '{print > "/dev/stderr"; printf "%04d\n",$0}' | grep -o . | pee 'sort -r' 'sort' | tr -d '\n' | sed 's/....$/-&/' | awk NF | bc; }; echo 9467 | juz 10 f
twitter.com#シェル芸 Q4解答これで良い?完全なワンライナーじゃなくて関数作ったけど。
— ぱぴろんちゃん🥺 (@papiron) 2021年4月24日
f(){ awk '{printf "%04d\n",$0}' | grep -o . | pee 'sort -r' 'sort' | tr -d '\n' | sed 's/....$/-&/' | awk NF | bc; }; echo 9467 | juz 10 f
関数ではなくシェルスクリプトとして保存して実行すれば動いた。
$ echo "awk '{print > \"/dev/stderr\"; printf \"%04d\n\",\$0}' | grep -o . | pee 'sort -r' 'sort' | tr -d '\n' | sed 's/....$/-&/' | awk NF | bc" > f; echo 497 | juz 10 sh f |& xargs 497 9261 8352 6174 6174 6174 6174 6174 6174 6174 6174
juz
コマンドなどのglueutilsはこちら。juz
コマンドは残念ながらmacOSではコンパイル出来なかった。(ヘッダーファイルprctl.h
がmacOSには付属していないため)
github.com
Q5
小問1
2つの素数を並べて、その差で等差数列を作る。とりあえずはこんな感じ。
$ seq 1000 | factor | awk 'NF==2{print $2}' | shuf | head -n 2 | sort -n | xargs | awk '{printf "%d %d ",$1,$2;a=$2;d=$2-$1;for(i=1;i<=4;i++){a=a+d;printf a" "};print ""}' 7 823 1639 2455 3271 4087
小問2
こちらは上記のワンライナーを無限ループさせれば良い。
$ while true; do seq 1000 | factor | awk 'NF==2{print $2}' | shuf | head -n 2 | sort -n | xargs | awk '{printf "%d %d ",$1,$2;a=$2;d=$2-$1;for(i=1;i<=4;i++){a=a+d;printf a" "};print ""}'; done 557 593 629 665 701 737 631 733 835 937 1039 1141 2 19 36 53 70 87 41 593 1145 1697 2249 2801 167 193 219 245 271 297 (以下省略)
Q6
出力結果の各列を素因数分解し、素数の数が6つになる行を探せば良い。具体的には、Q5小問1のワンライナーで出力される結果を各列素因数分解し、素数の数が6個になる場合を探す。
$ Q5小問1のワンライナー | factor | awk '{$1="";print}' | tr '\n' ' ' 41 127 3 71 13 23 5 7 11 3 157 【この出力が6列ならば求める結果になる】
while
で無限ループを作った解答がこちら。
$ while true; do seq 1000 | factor | awk 'NF==2{print $2}' | shuf | head -n 2 | sort -n | xargs | awk '{printf "%d %d ",$1,$2;a=$2;d=$2-$1;for(i=1;i<=4;i++){a=a+d;printf a" "};print ""}' | factor | awk '{$1="";print}' | tr '\n' ' ' | awk 'NF==6'; done 7 157 307 457 607 757 229 619 1009 1399 1789 2179 (以下省略)
上田会長の解答例ではteip
コマンドを使ったものがあった。teip
コマンドは「ぐれさん」の名前でコミュニティ活動されている方が作成された。
github.com
teip
コマンドは、特定の列に対してフィルタコマンドの処理を適用出来る。先日の業務でもこのコマンドが欲しい事例があった。2列目のデータのみにsed
コマンドの処理を適用する例はこちら。
$ echo a b c | teip -f 2 sed 's/.*/(&)/' a (b) c
juz
コマンドと合わせ技だとこんなことが出来る。
$ echo a b c | juz 10 teip -f 2 sed 's/.*/(&)/' a ((((((((((b)))))))))) c
この問題でのteip
コマンドの使い道は次の通り。横に並んだ数列に対してfactor
コマンドを適用すると縦に並んでしまうが、teip
コマンドを併用すると横に並べたまま出力出来る。
$ seq 2 5 | xargs | factor 2: 2 3: 3 4: 2 2 5: 5 $ seq 2 5 | xargs | teip -f 1-4 factor 2: 2 3: 3 4: 2 2 5: 5
teip
コマンドを使うと、素数の数を数える部分を下記のように書くことが出来る。1列目と2列目は素数なのが分かっているため、3列目以降のみ適用している。
$ Q5小問1のワンライナー | teip -f 3-6 factor 383 571 759: 3 11 23 947: 947 1135: 5 227 1323: 3 3 3 7 7 【この出力が10列なら求める結果になる】
Q7
これは勉強会の時間内ではギブアップ。後日トライした結果がこちら。
twitter.com#シェル芸 Q7のコッホ曲線やっとこさ出来た、、、距離やラジアンを使って地道に座標の計算をawkで頑張る問題という事がやっとこさ理解出来た。 pic.twitter.com/udd0klKIMM
— ぱぴろんちゃん🥺 (@papiron) 2021年5月2日
2つの座標について、距離と角度を使って追加する中間点を3つ計算し、座標データを追加していく処理をおこなう。 2つの座標を(x1,y1)、(x5,y5)とし、3つの中間点を(x2,y2)(x3,y3)(x4,y4)とする。 (x2,y2)と(x4,y4)は、(x1,y1)と(x5,y5)間を3等分した位置にある。
下記計算処理の手順を、2から6まで毎行毎に繰り返していけば良い。
- 1列目のデータの場合は、データをx1とy1に入力。
- 現在の行のデータを(x5,y5)に代入
- (x2,y2)を計算。(x1,y1)と(x5,y5)間で1:2の場所にある。
- (x3,y3)を計算。(x2,y2)を基準にする。(x1,y1,)(x5,y5)間の距離の3分の1を半径とし、60度(π/3ラジアン)回転した位置にある。
- (x4,y4)を計算。(x1,y1)と(x5,y5)間で2:1の場所にある。
- (x5,y5)の値を(x1,y1)へ代入して次の行へ移動。手順2から繰り返す。
上記の処理をAWKの1行プログラミングで書いて関数として定義。関数をたくさん通すほどコッホ曲線の深度が深くなっていく。
【関数定義】 $ f(){ awk 'NR==1{x1=$1;y1=$2}NR!=1{print x1,y1;x5=$1;y5=$2;a=atan2(y5-y1,x5-x1);r=sqrt((x5-x1)^2+(y5-y1)^2)/3;x2=x1+r*cos(a);y2=y1+r*sin(a);print x2,y2;x3=x2+r*cos(a+3.141592/3);y3=y2+r*sin(a+3.141592/3);print x3,y3;print x1+2*r*cos(a),y4=y1+2*r*sin(a);x1=x5;y1=y5}END{print x1,y1}'; } $ cat triangle | f | gnuplot -e 'set terminal png;set size ratio -1;set output "./hoge.png";plot "-" w l' 【画像が作成される】 $ cat triangle | f | f | f | gnuplot -e 'set terminal png;set size ratio -1;set output "./hoge.png";plot "-" w l' 【深度がより深い画像が作成される】
TLと二次会
LightningTalkは2つ発表があった。シェルを書いていて(シェルスクリプトでは無い!)付随するコマンド作った話題と、コマンドを使う勉強のモチベーションをアップ!(某〇〇ではない!)する話題。 www.youtube.com
二次会は初めてのgatherを体験。ゲーム感覚で面白い。
終わりに
久しぶりにシェル芸勉強会の参加記事を書いた。色々書こうとすると結構時間がかかってしまうが、アウトプットしておくと気分的にも良い。 コロナ禍の影響はいつまで続くのか分からないけど、リモート参加の経験値を上げておく機会だと思うことにしよう。 最後になったが、問題作成と配信をしていただく上田会長、毎回本当にお世話になります。参加者の方々もお疲れ様でした。