先日実施された、第35回シェル芸勉強会に参加してきたのでその復習(第34回は腰をやって参加してないので、1個空いちゃったなー…)。 今回は、前半はそこまで厳しく無かったのだが、後半が結構難しかったので中々疲れた。

問題及び模範解答はこちら。最初に、問題等に使用するファイルをgitからcloneしておくといい。

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

Q1.

ちょっと前に話題になっていた、curlでアクセスするとアスキーアートのParty Parrotがターミナル上で動き出すサービスをの出力をファイルに書き出し、それを後からターミナル上で再生するという内容。 ターミナルのログを記録、そして再生…ときたら、やはりここはscriptコマンドの出番である。

script --timing=parrot.log.time -c 'curl parrot.live' parrot.log
scriptreplay parrot.log -t parrot.log.time

ちなみに、terminal-parrotというコマンドもあるようなので、ローカルで同じような処理を見るならこれを使うのもありかもしれない。

Q2.

以下のような内容の、「herohero」というファイルがある。

1へ
7ろ
9へ
13ろ

これを、左側にある数字の行に出力するように加工するという問題。 最初は、とりあえずjoinコマンドを利用して以下のように解いてみた。

join -a 1 <(seq 13) <(cat herohero|nkf -Z|sed -r 's/[0-9]+/& /g') --nocheck-order | awk '{print $2}'
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.35$ cat herohero
1へ
7ろ
9へ
13ろ
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.35$ # 数字の全角→半角変換
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.35$ cat herohero | nkf -Z
1へ
7ろ
9へ
13ろ
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.35$ # スペースで区切る
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.35$ cat herohero | nkf -Z | sed 's/[0-9]+/& /g'
1へ
7ろ
9へ
13ろ
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.35$ cat herohero | nkf -Z | sed -r 's/[0-9]+/& /g'
1 へ
7 ろ
9 へ
13 ろ
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.35$ # joinで結合する(本来はこの書き方だと動かないはずなのだが、今回は想定した通りの動きをした)
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.35$ join -a 1 <(seq 13) <(cat herohero|nkf -Z|sed -r 's/[0-9]+/& /g') --nocheck-order
1 へ
2
3
4
5
6
7 ろ
8
9 へ
10
11
12
13 ろ
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.35$ # 2列目のみ抽出
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.35$ join -a 1 <(seq 13) <(cat herohero|nkf -Z|sed -r 's/[0-9]+/& /g') --nocheck-order | awk '{print $2}'
へ

ろ

へ

ろ

まぁ、単純にjoinで結合できるようにheroheroを加工して、それをseqとjoinしてから2列目のみ出力させるといった感じだ。 (なお、復習中にひょんなことからjoinでこの記述方法だとうまくいかない場合があることを知った(むしろ、なぜこの書き方で動いたのかわからない)ので、良い子は真似しちゃダメな書き方なので注意)

で、いつもどおり @ebanさん がすごく短くてキレイな処理を書かれていた。 処理自体がシンプルでキレイなので、必然的にコードも短くなるという…。やはり流石である。

blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.35$ cat herohero
1へ
7ろ
9へ
13ろ
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.35$ cat herohero | nkf -Z
1へ
7ろ
9へ
13ろ
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.35$ cat herohero | nkf -Z | sed 's/.$/c&/g;$ac'
1cへ
7cろ
9cへ
13cろ
c
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.35$ cat herohero | nkf -Z | sed 's/.$/c&/g;$ac' | sed -f- <(seq 13)
へ

ろ

へ

ろ

sedのcコマンド(指定した行の内容を後に続く文字列に書き換える)のコードを生成して、それをsedに-fで食わせることで対処するという…。 普通、こんな処理パッと思いつかないと思う。流石だなー。

Q3.

以下のようなファイル「data」の内容を集計するという問題。

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

時間内にやった際はawkを2回使うという対処だったので、とりあえずawk1回で処理をしてみる。

cat data | awk '{a[$1][$2]+=1}END{for(i in a){printf i" ";for(x in a[i]){printf(x":"a[i][x]" ")};print""}}'
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.35$ cat data | awk '{a[$1][$2]+=1}END{for(i in a){printf i" ";for(x in a[i]){printf(x":"a[i][x]" ")};print""}}'
1 A:2 B:3 
2 A:1 C:3 
3 B:3 C:1 D:1 
4 C:1

Q4.

ひらがなで名前っぽい文字列をランダムに生成してみせよ、という問題。 どうせなら名前をランダムに出してみたいと思ったので、pipで入れられるfakerコマンドを使って日本語の名前を生成して、それをひらがなに変換してみる。

PYTHONIOENCODING=UTF-8 faker first_name -r 10 | sed '/^$/d' | mecab -Oyomi | nkf --hiragana
blacknon@BS-PUB-DEVELOP:~$ faker first_name
涼平

blacknon@BS-PUB-DEVELOP:~$ faker first_name -r 10
千代

直子

和也

陽子

洋介

千代

翔太

明美

太郎

千代

blacknon@BS-PUB-DEVELOP:~$
blacknon@BS-PUB-DEVELOP:~$ # 環境変数で文字コードを指定しないとパイプで渡せないため、実行前に代入する
blacknon@BS-PUB-DEVELOP:~$ PYTHONIOENCODING=UTF-8 faker first_name -r 10 | sed '/^$/d'
康弘
舞
学
太一
真綾
真綾
太郎
舞
香織
七夏
blacknon@BS-PUB-DEVELOP:~$
blacknon@BS-PUB-DEVELOP:~$ # mecabで読みを取得する
blacknon@BS-PUB-DEVELOP:~$ PYTHONIOENCODING=UTF-8 faker first_name -r 10 | sed '/^$/d' | mecab -Oyomi
ヤスヒロ
ユミコ
ショウタ
ナオト
チミ
ヒロシフトシ
ミキ
サトカ
サトタロウ
マイ
blacknon@BS-PUB-DEVELOP:~$
blacknon@BS-PUB-DEVELOP:~$ # ひらがなに変換
blacknon@BS-PUB-DEVELOP:~$ PYTHONIOENCODING=UTF-8 faker first_name -r 10 | sed '/^$/d' | mecab -Oyomi | nkf --hiragana
はるか
あつし
みのる
かな
くみこ
つぶせしん
かな
がく
れい
ひろし

Q5.

おなじみの文字列「響け!ユーフォニアム」で、真ん中2文字を削りながら砂時計の形に整形するという問題。

響け!ユーフォニアム
 響け!ユォニアム
  響け!ニアム
   響けアム
    響ム
     
     
    ム響
   ムアけ響
  ムアニ!け響
 ムアニォユ!け響
ムアニォフーユ!け響

普通ならawkとか使って処理するのだが、最近はbashのパラメータ展開がマイブームなのでそれで処理をする。

echo 響け!ユーフォニアム | (read x;export x=$x;seq 0 $((${#x}/2))|tac|xargs -I@ bash -c 'y=$(eval printf " "%.s {0..$((${#x}/2-@))}) && [ @ -eq 0 ] || echo $y${x:0:@}${x: -$((@))}$y';echo) | tee >(tac|rev) | sed 's/^.//g'
blacknon@BS-PUB-UBUNTU-01:~$ echo 響け!ユーフォニアム | (read x;export x=$x;seq 0 $((${#x}/2))|tac|xargs -I@ bash -c 'y=$(eval printf " "%.s {0..$((${#x}/2-@))}) && [ @ -eq 0 ] || echo $y${x:0:@}${x: -$((@))}$y';echo) | tee >(tac|rev) | sed 's/^.//g'
響け!ユーフォニアム 
 響け!ユォニアム  
  響け!ニアム   
   響けアム    
    響ム     

    ム響     
   ムアけ響    
  ムアニ!け響   
 ムアニォユ!け響  
ムアニォフーユ!け響

(長くはなるのだけど)こういう事もできるということで…

Q6.

素因数分解をした時、23より大きい素因数を含まない素数を1985個抽出する、という問題。 なんでも、Q8とセットで1985年の数学オリンピックの問題から取ってきたらしい。なるほどー…。

とりあえず解いてみるべかなと思ったのだが、自分の書いたやつよりぱぴろんちゃんさん(さかなクンさんみたいだな…)のコードのがキレイだったのでそれを紹介しておく。

seq infで無限に数字を生成してくれる(確かmanに記載のない隠しオプション的な代物)ので、それをfactorにパイプで渡している。 で、factorでは総因数は左から昇順で記述されるので、NF(行末)の値が23より小さい行のみをawkで取得し、その結果を頭から1985個取ってくるといったやり方をしている。

Q7.

素数番目の文字のみを抜き出してやると意味の通った文字列を生成する、という問題。 素数番目以外の文字については何を入れても大丈夫らしい。

とりあえず、Q2のebanさんのやり方を参考にして以下のようにsedに食わせる処理を生成する方法でやってみた。

echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|sed -f &lt;(paste -d' ' &lt;(echo おなかすいたでごわす|grep -o .|sed 's/^./s|.|&|/') &lt;(seq 30|factor|awk 'NF==2{print $2}'))
blacknon@BS-PUB-UBUNTU-01:~$ echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|sed -f &lt;(paste -d' ' &lt;(echo おなかすいたでごわす|grep -o .|sed 's/^./s|.|&|/') &lt;(seq 30|factor|awk 'NF==2{print $2}'))
xおなxかxすxxxいxたxxxでxごxxxわxxxxxすx

Q8.

Q6からの続きで、Q6の方法で作成した自然数をファイルaに保存し、この中から4つ数字を選んで掛け算したとき、その値がある自然数の4乗になっている組み合わせを1個以上探せ、という問題。   何かしらの数字の4乗を含む数字であればいいらしい。ぐれさんのアプローチの仕方(先に何かしらの4乗の数字のみを抽出し、それをかけ合わせれば何かしらの自然数の4乗となる数字が得られる)が非常に効率が良さそうだった。

seq inf|factor|awk '$NF<=23{gsub(":","",$1);print $1}'|head -1985 > a
cat a | factor | sed 's/$/ /g' | grep -E '([0-9]+ )\1{3}' | shuf -n4 | awk 'BEGIN{a=1}{gsub(":","",$1);a=a*$1;b=b" "$1}END{printf(b" ");print a|"factor"}'
blacknon@BS-PUB-UBUNTU-01:~$ cat a | factor | sed 's/$/ /g' | grep -E '([0-9]+ )\1{3}' | shuf -n4 | awk 'BEGIN{a=1}{gsub(":","",$1);a=a*$1;b=b" "$1}END{printf(b" ");print a|"factor"}'
 1701 18480 7371 8736 2024162230970880: 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 5 7 7 7 7 11 13 13
blacknon@BS-PUB-UBUNTU-01:~$ cat a | factor | sed 's/$/ /g' | grep -E '([0-9]+ )\1{3}' | shuf -n4 | awk 'BEGIN{a=1}{gsub(":","",$1);a=a*$1;b=b" "$1}END{printf(b" ");print a|"factor"}'
 15504 2106 4320 2448 345300563312640: 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 5 13 17 17 19

とりあえず、今回もいつもどおり難しかった。