第35回シェル芸勉強会 - 解答(リモート参加)(普通版)

第35回シェル芸勉強会で寝坊してリモート参加しました。
ドタキャンして申し訳ありません。。。

問題と私の解答

Q1

curl parrot.live で表示されるオウムをファイルに生け捕りにして、ファイルから再生してください

A1
timeout 10 curl http://parrot.live  > a; seq `wc -l a | cut -d\  -f1` | sed 's#.*#sed &\\!d a#;0~18s#$#;sleep 0.0001#' | sh

https://twitter.com/nogiro_iota/status/982474241588150277
https://twitter.com/nogiro_iota/status/982474858209558528

解説

curl で parrot.live を叩くと無限に踊ってくれるので、timeout を使って途中で止めます。
最後の | sh までは ↓ のように各行を出力しながら sleep するスクリプトを生成しています。

$ seq `wc -l a | cut -d\  -f1` | sed 's#.*#sed &\\!d a#;0~18s#$#;sleep 0.0001#' | head -n 20                      sed 1\!d a
sed 2\!d a
sed 3\!d a
sed 4\!d a
sed 5\!d a
sed 6\!d a
sed 7\!d a
sed 8\!d a
sed 9\!d a
sed 10\!d a
sed 11\!d a
sed 12\!d a
sed 13\!d a
sed 14\!d a
sed 15\!d a
sed 16\!d a
sed 17\!d a
sed 18\!d a;sleep 0.0001
sed 19\!d a
sed 20\!d a
感想

他の方々の解答によると、画面クリア・色変えの制御コードのある行の前でスリープするとキレイに表示できたようです。
Mac OS X だとファイルに書き出すのが難しいらしいですが、WSL (Ubuntu 16.04) だったため普通にリダイレクトできました。

Q2

次のようなファイルheroheroがあります。

$ cat herohero 
1へ
7ろ
9へ
13ろ

ひらがなを左側に書いてある数字の行に持って行き、次のような出力に変換してください。

へ





ろ

へ



ろ
A2.1
cat herohero | sed 'y#0123456789#0123456789#' | sed 's#^[0-9]*#& #' | awk 'BEGIN{i=1}{for (;i<=$1;++i){print ""}printf $2}' | sed 1d | awk 1

https://twitter.com/nogiro_iota/status/982476301880606720

解説

awk の for 文で指定数字の前までに空行を出力しています。指定数字があったらそれを出力します。
(ちなみに sed の y コマンドで全部の数字を列挙しているのは、一般性を気にしてるのではなく読むのがめんどくさかったからです。)

A2.2

| sh すればなんとかなる」版です。

nkf -Z herohero | sed 's#^[0-9]*#& #' | sort -Vr | awk 'NR==1{print "yes|head -"$1}{print "sed "$1"s_y_"$2"_"}' | paste -s -d\| | sh | tr -d y

https://twitter.com/nogiro_iota/status/982478641773740032

解説

| sh の前までで↓を出力しています。 yes の出力を消していないので最後に tr で消します。

$ nkf -Z herohero | sed 's#^[0-9]*#& #' | sort -Vr | awk 'NR==1{print "yes|head -"$1}{print "sed "$1"s_y_"$2"_"}' | paste -s -d\|
yes|head -13|sed 13s_y_ろ_|sed 9s_y_へ_|sed 7s_y_ろ_|sed 1s_y_へ_
A2.3
nkf -Z herohero | { tee >(tr -dc 0-9\\n | tac | head -1 | xargs seq); } | sort -n | sed 's#[0-9]*#& #' | tac | uniq -w2 | cut -d\  -f2 | tac

https://twitter.com/nogiro_iota/status/982480115119484928

解説

会長の解説を聞いていて思いついた解答です。
前で sort- <(seq) していたものを{ tee >(seq); } | sortに置き換えました。
{ tee >(command); } でパイプラインを分岐することができますが、順序が不定になります(tac, sort 等の全部読み込んでから動作するコマンドは後から出力されますし、今回は後から sort するのでさらに大丈夫です)。
(後述の pee を使うと順序も決まるし変なイディオムがなくて便利。{} をなんで付けていたかは忘れました。)
seq の引数をマジックナンバーにしないために地味に長くなっています。

感想

A2.2 は sed の c コマンドを使っておけばもうちょっとキレイにかけた模様です。

nkf -Z herohero | sed 's#^[0-9]*#& #' | sort -Vr | awk 'NR==1{print "yes \"\"| head -"$1}{print "sed "$1"c"$2}' | paste -s -d\| | sh

nkf がいつまでも覚えられない。。。

Q3

次のようなファイルdataがあります。

$ cat data
1 A
1 B
2 C
2 C
1 B
3 C
4 C
3 B
3 B
3 D
3 B
1 B
2 A
1 A
2 C

集計して次のような出力を得てください。

1 A:2 B:3
2 A:1 C:3
3 B:3 C:1 D:1
4 C:1
A3
cat data | sort | uniq -c | sort -k2 | awk '$2!=p2{print p2}{printf $3":"$1" "}{p2=$2}END{print p2}' | sed -E 's#(.*) ([0-9])$#\2 \1#'

https://twitter.com/nogiro_iota/status/982482659711463424

解説

順番を保証しないとダメなのかと思ったため、連想配列を使わず 2 行ずつ見て改行しました。

Q4

ひらがなで名前っぽい単語をランダムに生成してみてください。

A4.1
w3m -dump http://douseidoumei.com/heisei/h_name/15/001.htm | sed '1,/名前リスト/d' | sed '1,3d' | awk '$0=$3' | mecab | awk -F, 'NF!=1{printf $NF}NF==1{print ""}' | nkf --hiragana | sed \$d

https://twitter.com/nogiro_iota/status/982486297670373376

解説

思いつかなかったので、Web スクレイピングで名前を拾って来ました。

感想

mecab を使うという発想がまず出なかった。。。

Q5

echo 響け!ユーフォニアム からはじめて、次のような出力を得てください。なお、出題者はこのアニメを見たことがありません。

響け!ユーフォニアム
 響け!ユォニアム
  響け!ニアム
   響けアム
    響ム
     
     
    ム響
   ムアけ響
  ムアニ!け響
 ムアニォユ!け響
ムアニォフーユ!け響
A5.1
echo '響け!ユーフォニアム' | awk '{l=length;for(i=0;i<=l/2;++i)print "echo "$0"|sed -E \"s#(.{"l/2-i"}).{"i*2"}#\\1#\""}' | sh | awk 'NR==1{l=length/2}{for(i=1;i<NR;i++){printf " "}print}' | { tee >(tac|rev); } | sed -E 's#([^ ]+)( +)#\2\1#'

https://twitter.com/nogiro_iota/status/982494450722136064

解説

折返し反転をしている { tee >(tac|rev); } 部分以外は awk でゴリ押ししました。
前述のとおり tac が入っているため順番どおりに出ているようです。
なお、解答者もこのアニメを見たことがありません。

A5.2
echo '響け!ユーフォニアム'|awk '{l=length;for(i=0;i<=l/2;++i)print "echo "$0"|sed -E \"s#(.{"l/2-i"}).{"i*2"}#\\1#\""}'|sh|awk 'NR==1{l=length/2}{for(i=1;i<NR;i++){printf " "}print}'|vim -es /dev/stdin +%p +'1,$!tac|rev' +'%p|q!'|sed -E 's#([^ ]+)( +)#\2\1#'

https://twitter.com/nogiro_iota/status/982495788260405248

解説

折返し部分を vim シェル芸に置き換えました。出力を分けてできるので非常に便利でした。

感想

pee というコマンドが moreutils にあるらしく、tee >(command)pee cat command と書くことができて、出力順序まで指定できました。
↓ わかりがあります。


Q6

素因数分解したときに23より大きい素因数を持たない自然数を1985個抽出してください。

A6
yes | nl -nln | cut -f1 | factor | awk '$NF<23' | head -n 1985 | cut -f1 -d: | head

https://twitter.com/nogiro_iota/status/982498968838914048

感想

ちょうど↓の記事を読んでいたので1人で盛り上がってました。
鳩ノ巣原理を使う数学オリンピックの問題 | 高校数学の美しい物語

factor で出力される素因数は右側が一番大きいの知らなかった。。。

Q7

素数番目の文字を抽出すると意味のある語句になっているような文字列を作成してください。例を示します。(素数番目でない文字は特に凝る必要はありません。同じ文字でも大丈夫です。)

うそすんうんだいうんいんすんこうき

その後、その語句を抽出してください。

解なし

勉強会中には解答は思いつきませんでした。

感想

「ゴリ押しするしかないかなー」と思っていたら次の問題に入ってました。

Q8

Q6の方法で作成した自然数をファイルaに保存し、この中から4つ数字を選んで掛け算したとき、その値がある自然数の4乗になっている組み合わせを1個以上探してください。

A8
yes 'shuf a|head -n 4|paste -s -d\*|sed '\''s%.*%print "&\\n";&%'\''|bc|sed '\''1~2s%^%echo %;0~2s%.*%factor &|sed "s_^[^:]*: __"|tr " " \\\\n|paste - - - -|awk "!($1==$2\&\&$1==$3\&\&$1==$4)"%'\'|sh|tr \" \'|sh|awk 'p~"*"&&$0~"*"{print p;exit}{p=$0}'

https://twitter.com/nogiro_iota/status/982509662233743360
https://twitter.com/nogiro_iota/status/982512441736101888

解説

「ファイル a からランダムに4つ数を抽出して、それらの掛け算の計算式そのものと計算結果を bc で出力、計算式は echo して、計算結果は factor 後に素因数を 4 列にして行内が全部一緒だったら消すコマンドを生成する」というコマンドを yes で無限に生成して、2回 | sh しています。その後に、(計算式が連続している場合は 4 つ組の素因数が全部おなじだったということなので)計算式が連続していたら上の計算式を出力します。
長すぎて解説になってませんね。

1 つ目の | sh の前の出力は以下のとおりです。

$ yes 'shuf a|head -n 4|paste -s -d\*|sed '\''s%.*%print "&\\n";&%'\''|bc|sed '\''1~2s%^%echo %;0~2s%.*%factor &|sed "s_^[^:]*: __"|tr " " \\\\n|paste - - - -|awk "!($1==$2\&\&$1==$3\&\&$1==$4)"%'\' | head -n 2
shuf a|head -n 4|paste -s -d\*|sed 's%.*%print "&\\n";&%'|bc|sed '1~2s%^%echo %;0~2s%.*%factor &|sed "s_^[^:]*: __"|tr " " \\\\n|paste - - - -|awk "!($1==$2\&\&$1==$3\&\&$1==$4)"%'
shuf a|head -n 4|paste -s -d\*|sed 's%.*%print "&\\n";&%'|bc|sed '1~2s%^%echo %;0~2s%.*%factor &|sed "s_^[^:]*: __"|tr " " \\\\n|paste - - - -|awk "!($1==$2\&\&$1==$3\&\&$1==$4)"%'

その後もすごい泥縄式なので、以下に各段階の出力結果を載せます。

$ shuf a | head -n 4 | paste -s -d\* | sed 's%.*%print "&\\n";&%'
print "6885*10143*8415*918\n";6885*10143*8415*918
$ shuf a | head -n 4 | paste -s -d\* | sed 's%.*%print "&\\n";&%' | bc
3366*9728*1260*384
15843073720320
$ shuf a | head -n 4 | paste -s -d\* | sed 's%.*%print "&\\n";&%' | bc | sed '1~2s%^%echo %;0~2s%.*%factor & | sed "s_^[^:]*: __" | tr " " \\\\n | paste - - - - | awk "!($1==$2\&\&$1==$3\&\&$1==$4)"%'
echo 13754*882*7200*2310
factor 201763257696000 | sed "s_^[^:]*: __" | tr " " \\n | paste - - - - | awk "!($1==$2&&$1==$3&&$1==$4)"
$ shuf a | head -n 4 | paste -s -d\* | sed 's%.*%print "&\\n";&%' | bc |sed '1~2s%^%echo %;0~2s%.*%factor & | sed "s_^[^:]*: __" | tr " " \\\\n | paste - - - - | awk "!($1==$2\&\&$1==$3\&\&$1==$4)"%' | tr \" \' | sh
4394*9375*598*10944
3       3       3       5
19      23

本来は shuf a | ~は yes で供給され無限回実行されるので、以下の出力は無限回出ます。

4394*9375*598*10944
3       3       3       5
19      23

出力を観察してもらうと、素因数としては 2 が含まれるはずですが、4 つおなじ値があったときは awk "!($1==$2\&\&$1==$3\&\&$1==$4)" で除外しています。
そのため、「ある自然数の4乗になっている組み合わせ」になっている場合素因数はなにも出力されないので、計算式が連続して出力されます。
awk で上の計算式を抽出して終了すれば結果が得られます。

感想

泥縄式すぎて解説できる気がしない。。。

#echo ってツイッターAmazon Echo のアイコンが出てるのは私だけ・・・?
https://twitter.com/nogiro_iota/status/982510122638262273