先日実施された第44回シェル芸勉強会に出席してきたので、その復習。 今回は実用的な内容…ということだったのだが、そうはならんかったらしい(´・ω・`)。 冒頭で、awkなどでゴリゴリ書いていく問題がメインらしいので、多分難しめなんだろうなぁと思ったけど、おわってみたらやはり難しい問題が多かった。

問題および模範解答はこちら。あと、問題を解くに当たって必要になるファイルは以下のコマンドで取得してくる。

git clone https://github.com/ryuichiueda/ShellGeiData

Q1.

数独の問題で、マップ?のデータから各行列にどのような値が入ってるかを一覧化する問題。 数独がやったことないのでよくわからないのだけど、とりあえず第一フィールドが行番号、第二フィールドが列番号、第三フィールドが区画(3x3のグリッドに適当に番号をつけたもの)、第四フィールドが値となるようにすればいいようだ。

ひとまず、以下のようにawkで処理することで実現できる。

awk -F '' '{for(i=0;i<NF;i++){print (NR-1),i,int(i/3)+int(NR/3)*3,$(i+1)}}' sudoku
[DOCKER][root@9ff235cb5592][/ShellGeiData/vol.44]
(`・ω・´) < awk -F '' '{for(i=0;i<NF;i++){print (NR-1),i,int(i/3)+int(NR/3)*3,$(i+1)}}' sudoku | head
0 0 0 5
0 1 0 3
0 2 0 *
0 3 1 *
0 4 1 7
0 5 1 *
0 6 2 *
0 7 2 *
0 8 2 *
1 0 0 6

この問題については、awkでちゃんと書いてあげるのが一番良さそうだ。

Q2.

Q1から続きの問題で、Q1で作ったファイルをaというファイル名で保存して、さらにaのデータをもとに「4列目が*になっている行に対して、5列目以降に4列目に入らない数字を入れてbというファイルで保存しろ」という問題。 これについては、awkでゴリゴリ回答を作成するよりは外部コマンドを生成してbashに食わせるといったやり方のほうが楽そうだったので、その方法で対処した。

cat a | awk '{if ($NF !~/^[0-9]+$/){print "echo \""$0"\"","$(seq 1 9|grep -v -f <(cat a|grep ^"$1"|awk \"{print \\$NF}\"|grep -o [0-9]))"}else{print "echo "$0}}' | bash
[DOCKER][root@9ff235cb5592][/ShellGeiData/vol.44]
(`・ω・´) < cat a | awk '{if ($NF !~/^[0-9]+$/){print "echo \""$0"\"","$(seq 1 9|grep -v -f <(cat a|grep ^"$1"|awk \"{print \\$NF}\"|grep -o [0-9]))"}else{print "echo "$0}}' | head
echo 0 0 0 5
echo 0 1 0 3
echo "0 2 0 *" $(seq 1 9|grep -v -f <(cat a|grep ^0|awk "{print \$NF}"|grep -o [0-9]))
echo "0 3 1 *" $(seq 1 9|grep -v -f <(cat a|grep ^0|awk "{print \$NF}"|grep -o [0-9]))
echo 0 4 1 7
echo "0 5 1 *" $(seq 1 9|grep -v -f <(cat a|grep ^0|awk "{print \$NF}"|grep -o [0-9]))
echo "0 6 2 *" $(seq 1 9|grep -v -f <(cat a|grep ^0|awk "{print \$NF}"|grep -o [0-9]))
echo "0 7 2 *" $(seq 1 9|grep -v -f <(cat a|grep ^0|awk "{print \$NF}"|grep -o [0-9]))
echo "0 8 2 *" $(seq 1 9|grep -v -f <(cat a|grep ^0|awk "{print \$NF}"|grep -o [0-9]))
echo 1 0 0 6

[DOCKER][root@9ff235cb5592][/ShellGeiData/vol.44]
(`・ω・´) < cat a | awk '{if ($NF !~/^[0-9]+$/){print "echo \""$0"\"","$(seq 1 9|grep -v -f <(cat a|grep ^"$1"|awk \"{print \\$NF}\"|grep -o [0-9]))"}else{print "echo "$0}}' | bash | head
0 0 0 5
0 1 0 3
0 2 0 * 1 2 4 6 8 9
0 3 1 * 1 2 4 6 8 9
0 4 1 7
0 5 1 * 1 2 4 6 8 9
0 6 2 * 1 2 4 6 8 9
0 7 2 * 1 2 4 6 8 9
0 8 2 * 1 2 4 6 8 9
1 0 0 6

Q3.

Q3は、Q2で生成したファイルであるbに対して「5列目の値を4列目に入るであろう値のリストに書き換える」という処理をしたファイルcを作成する、という問題。 そもそも数独のルールを理解していないので何を入れるんだかさっぱりわからない+数独について調べたけどいまいちよくわからなかったので回答のしようがないので、模範解答の内容を転記しておく。

cat b | awk 'NF==4;NF!=4{for(i=1;i<=9;i++){
for(j=5;j<=NF;j++){if(i==$j){j=NF+3}} if(j!=NF+4)$4=$4 " "i
};print $1,$2,$3,$4}'
[DOCKER][root@9ff235cb5592][/ShellGeiData/vol.44]
(`・ω・´) < cat b | awk 'NF==4;NF!=4{for(i=1;i<=9;i++){
for(j=5;j<=NF;j++){if(i==$j){j=NF+3}} if(j!=NF+4)$4=$4 " "i
};print $1,$2,$3,$4}' | head
0 0 0 5
0 1 0 3
0 2 0 * 1 2 4
0 3 1 * 2 6
0 4 1 7
0 5 1 * 2 4 6 8
0 6 2 * 1 4 8 9
0 7 2 * 1 2 4 9
0 8 2 * 2 4 8
1 0 0 6

Q4.

Q4では、Q3で作成したcをもとに最初のファイル「sudoku」のフォーマットでファイルを作成するという問題。 その際、Q3の段階で4列目に1個しかファイルが入らない行についてはその内容も適用する必要がある。これについては、awkでちゃちゃっとやってしまうのがいいだろうということで、そのまま処理をした。

cat c | awk '{x=$4;if(x=="*"&&NF==5){x=$5};print x}' | xargs -n 9 | sed 's/ //g'
[DOCKER][root@9f6fb38551e6][/ShellGeiData/vol.44]
(`・ω・´) < cat c | awk '{x=$4;if(x=="*"&&NF==5){x=$5};print x}' | xargs -n 9 | sed 's/ //g'
53**7****
6**195***
*98****6*
8***6***3
4**853**1
7***2***6
*6***7284
***419*35
****8**79

 Q5.

Q5は数学の問題だった。積分なのだが、社会人になってからそういうのはやってなかったのでもはやさっぱり…。 とりあえず模範解答だけ転記しておくことにする。

seq 0 49 | awk '{print $1/100, $1/100+0.01}' | awk '{print log(cos($1)), log(cos($2))}' | awk '{print ($1+$2)/2*0.01}' | awk '{a+=$1}END{print a}'

Q6.

Q6はウンコ系の問題。 体感だけど、(案の定というか)この問題だけ正答率がすごく高かった気がする。 ファイル「speech」の空行に対し、「speeck2」の内容を上から順に差し込んでいくという問題。とりあえず、自分は以下のような回答で処理をした。

cat speech | awk 'BEGIN{i=1}length($0)==0{print "sed -n "i"p speech2";i++}length($0)>0{print "echo "$0}' | bash
[DOCKER][root@9ff235cb5592][/ShellGeiData/vol.44]
(`・ω・´) < cat speech | awk 'BEGIN{i=1}length($0)==0{print "sed -n "i"p speech2";i++}length($0)>0{print "echo "$0}' | bash
このうんこを作った
のは誰だあっ!!う
んこも休み休み言え
春はあけぼの。夏は
夜。秋は夕暮れ。冬
はうんこハァ テレ
ビも無ェ、うんこも
無ェ、生まれてこの
かた見だごとア無ェ
やつはとんでもない
ものを盗んでいきま
した。あなたのうん
こですお前それうん
こでも同じ事言えん
の?疲れからか、

この問題に関しては、くんすとさんの回答が非常にきれいだった。 awkのgetlineを利用することで、外部のファイルを上から順に差し込めるというのは知らなかった。

cat speech | awk 'NF==0{getline< "speech2"}{print}'
[DOCKER][root@9ff235cb5592][/ShellGeiData/vol.44]
(`・ω・´) < cat speech | awk 'NF==0{getline< "speech2"}{print}'
このうんこを作った
のは誰だあっ!!う
んこも休み休み言え
春はあけぼの。夏は
夜。秋は夕暮れ。冬
はうんこハァ テレ
ビも無ェ、うんこも
無ェ、生まれてこの
かた見だごとア無ェ
やつはとんでもない
ものを盗んでいきま
した。あなたのうん
こですお前それうん
こでも同じ事言えん
の?疲れからか、

Q7.

簡単なRSA暗号を施されたファイル「message」をシェル芸で元に戻そうという内容。 ASCIIコードの文字一つ一つを5乗して437で割った余りを10進数で記述されており、各十進数を何乗かして437で割った余りをASCIIコードとして解釈するとメッセージが読めるらしい。

まずは各数字を200乗して、それを437で割る。 このとき、通常のawk(mawk)だとあまりに大きい数を扱おうとするとうまく行かない。

ebanさんのツイートで知ったのだが、こういった場合はgawkの-Mオプションを利用することで対処できるらしい。

cat message|fmt -1|gawk -M '{print ($1^191)%437}'

これを更に解読する。 RCA暗号であればこの数字は素数になると思わえるが、とりあえず200までの数字を使って計算する。 文字コードから文字列を出力する場合はprintfで%cを指定することで対処できるのでそれを利用する。

seq -f 'echo $(cat message|fmt -1|gawk -M '\''{printf ("%%c",($1^%g)%%437)}'\'')' 200 | bash
[DOCKER][root@9ff235cb5592][/ShellGeiData/vol.44]
(`・ω・´) < seq -f 'echo $(cat message|fmt -1|gawk -M '\''{printf ("%%c",($1^%g)%%437)}'\'')' 200 | bash | head
ĆŅzPĊƖ£YŅYņ
#Ĵ�ĚƏWŝ7Ĵ7U
ƮđżŪMX
ĻƓ#Ɠé
!ťƢØ!
1Ĥ`ホøĻĤĻ
¥GŞʼnĝƩÜCGCÀ
ƔşķdÑŴ�ĚşĚe
^Ũ_ċı½½
©Üèũ�Ŏש×Ě

これで200個分を計算できた。 ひとまず「unko」だろということでgrepをすると「unko_jyanai」というキーワードがヒットするので、これが正解(ちなみに数字は119)というのがわかるのだけど、もしunkoが入ってない場合はどうやって識別するのか?

ちょっと無理やりだけど、今回の場合だとASCIIだということが最初にわかっていることから、fileコマンドに出力を渡して識別させればいいということがわかる。

seq -f 'echo $(cat message|fmt -1|gawk -M '\''{printf ("%%c",($1^%g)%%437)}'\'')|tee >(file -)' 200 | bash | grep -B2 ASCII