シェル芸スパルタン演習(第14回シェル芸勉強会編)
ここ最近、Unixシェルに触る機会が減っており、CLIを使う能力の低下を感じていた。
また、近日中に、第25回シェル芸勉強会の開催が予定されており、参加するにあたり予習をしたいとも考えていた。
そこで、過去に開催されたシェル芸勉強会の問題を活用し、シェル芸力をつける演習をおこなうことにした。
今回は、問題として、第14回シェル芸勉強会の設問を用いた。
実行環境
Arch Linux 4.8.4-1-ARCH
GNU bash 4.3.46
GNU coreutils 8.25-2
GNU diffutils 3.5-1
GNU findutils 4.6.0-2
util-linux 2.28.2-1
grep (GNU grep) 2.26
GNU bc 1.06.95
sed (GNU sed) 4.2.2
gawk 4.1.4
xxd V1.10 27oct98
w3m 0.5.3+git20160414
usp Personal Tukubai 20160402 ( Open usp Tukubaiで代用可能)
シェル芸演習
問題文は、以下のURLから。
http://blog.ueda.asia/?p=4671
模範解答は、こちらから。
http://blog.ueda.asia/?p=4413
問1
100乗の計算の問題。
高精度が要求されているので、100!の式を生成して、bcに入力する。
01 #!/bin/sh
02
03 seq -s ' ' 1 100 |
04 tr ' ' '*' |
05 BC_LINE_LENGTH=0 bc -l
100!の正確な値の確認は、こちらで。 https://www.wolframalpha.com/input/?i=100!
以下、コピペ実行用のワンライナーコード。
seq -s ' ' 1 100 | tr ' ' '*' | BC_LINE_LENGTH=0 bc -l
問2
sed縛りのFizzBuzz問題。
実はこれ、どうしても分からなくて、模範解答を見てしまった…
GNU sedに数行おきにマッチさせるアドレス指定、"first\~step"なんて指定方法があったなんて、知らなかった…!!
01 #!/bin/sh
02
03 seq 100 |
04 sed '5~5cBuzz' |
05 sed '3~3s/[0-9]*/Fizz/'
以下、コピペ実行用のワンライナーコード。
seq 100 | sed '5~5cBuzz' | sed '3~3s/[0-9]*/Fizz/'
問3
素数判別の問題。
とはいえ、与えられた値は数値参照の形式になっているので、まずそれを10進数に直す必要がある。
printfコマンドで直した後、factorコマンドで素数判定をば。
01 #!/bin/sh
02
03 echo 0xaf 0x13 0x0d 0x24 0x58 |
04 xargs -n 1 printf '%d\n' |
05 factor |
06 awk 'NF==2{printf("0x%02x\n",$0)}'
以下、コピペ実行用のワンライナーコード。
echo 0xaf 0x13 0x0d 0x24 0x58 | xargs -n 1 printf '%d\n' | factor | awk 'NF==2{printf("0x%02x\n",$0)}'
問4
UTF-8な16進数ダンプを、元のテキストに復元する問題。
Vimに付属しているxxdコマンドを使えば、一発。
01 #!/bin/sh
02
03 echo e89fb9e3818ce9a39fe381b9e3819fe38184 |
04 xxd -r -p |
05 awk '1'
以下、コピペ実行用のワンライナーコード。
echo e89fb9e3818ce9a39fe381b9e3819fe38184 | xxd -r -p | awk '1'
問5
中身を水増しした 嫌がらせ ファイルを作る問題。
特殊デバイスファイル、/dev/zeroを利用して、10\^9-3-1バイト(3:aho, 1:\n)分、注入してやればOK。
プロセス置換で。
01 #!/bin/bash
02
03 cat <(echo aho) <(cat /dev/zero |fold -w 1 |head -n $(echo 10^9 -4 |bc) |tr -d '\n') > hoge
パイプを使って。
01 #!/bin/sh
02
03 cat /dev/zero |
04 fold -w 1 |
05 head -n $(echo 10^9-4 |bc) |
06 tr -d '\n' |
07 sed 'iaho' > hoge
以下、コピペ実行用のワンライナーコード。
実行する場合は、 無駄に大きいファイルが作成される事に注意。
cat <(echo aho) <(cat /dev/zero |fold -w 1 |head -n $(echo 10^9 -4 |bc) |tr -d '\n') > hoge
cat /dev/zero | fold -w 1 | head -n $(echo 10^9-4 |bc) | tr -d '\n' | sed 'iaho' > hoge
問6
日本の全ての山に関して、標高の高い順にリスト化する問題。
指定されたURLからHTMLソースを落とし、後はフィルタで頑張って加工するだけ。
01 #!/bin/sh
02
03 w3m -dump -cols 200 'http://ja.wikipedia.org/wiki/%E6%97%A5%E6%9C%AC%E3%81%AE%E5%B1%B1%E4%B8%80%E8%A6%A7_%28%E9%AB%98%E3%81%95%E9%A0%86%29' 2>/dev/null |
04 sed -n '/高さ順の100山リストは、/,/^脚注/{/除外した山/,/2,500 m以上の山/d; p}' |
05 awk '$1 ~ /^[0-9]{1,2}$/{print $2,$4; next} $3 ~ /^[0-9]/{print $1,$3}' |
06 tr -d ',' |
07 sort -k 2,2nr |
08 awk '{print NR,$0}'
以下、コピペ実行用のワンライナーコード。
w3m -dump -cols 200 'http://ja.wikipedia.org/wiki/%E6%97%A5%E6%9C%AC%E3%81%AE%E5%B1%B1%E4%B8%80%E8%A6%A7_%28%E9%AB%98%E3%81%95%E9%A0%86%29' 2>/dev/null | sed -n '/高さ順の100山リストは、/,/^脚注/{/除外した山/,/2,500 m以上の山/d; p}' | awk '$1 ~ /^[0-9]{1,2}$/{print $2,$4; next} $3 ~ /^[0-9]/{print $1,$3}' | tr -d ',' | sort -k 2,2nr | awk '{print NR,$0}'
問7
計算問題。
ただし、 計算結果を分数表記にする 点が、凄まじく難しい…
とりあえず、何とかやってみた…
01 #!/bin/sh
02
03 echo '1/4 + 2/5 + 7/16 - 5/9' |
04 sed 's/[+-]/\n&/g' |
05 sed 's/^+ //' |
06 tr -d ' ' |
07 tr '/' ' ' |
08 awk 'BEGIN{printf "echo "} {printf("%d ", $1); arr[NR]=$2} END{print ""; for(n in arr){print "factor",arr[n]}}' |
09 sh |
10 awk 'NR==1; 2<=NR{for(i=2;i<=NF;i++){num[$i]++}; for(n in num){print $1, n, num[n]}; delete num}' |
11 awk 'NR==1; 2<=NR{print $2^$3}' |
12 awk 'BEGIN{prod=1} NR==1; 2<=NR{prod*=$0; num[NR]=$0} END{for(i=2;i<=NR;i++){print num[i],prod}}' |
13 awk 'NR==1{for(i=1;i<=NF;i++){num[i]=$i}} 2<=NR{denom=$2; prod[NR-1] = $2/$1} END{for(i=1;i<=NR-1;i++){nume += num[i]*prod[i]}; print nume,denom}'
約分する方は、勘弁してくださいorz
以下、コピペ実行用のワンライナーコード。
echo '1/4 + 2/5 + 7/16 - 5/9' | sed 's/[+-]/\n&/g' | sed 's/^+ //' | tr -d ' ' | tr '/' ' ' | awk 'BEGIN{printf "echo "} {printf("%d ", $1); arr[NR]=$2} END{print ""; for(n in arr){print "factor",arr[n]}}' | sh | awk 'NR==1; 2<=NR{for(i=2;i<=NF;i++){num[$i]++}; for(n in num){print $1, n, num[n]}; delete num}' | awk 'NR==1; 2<=NR{print $2^$3}' | awk 'BEGIN{prod=1} NR==1; 2<=NR{prod*=$0; num[NR]=$0} END{for(i=2;i<=NR;i++){print num[i],prod}}' | awk 'NR==1{for(i=1;i<=NF;i++){num[i]=$i}} 2<=NR{denom=$2; prod[NR-1] = $2/$1} END{for(i=1;i<=NR-1;i++){nume += num[i]*prod[i]}; print nume,denom}'
問8
ポキポキポッキーな問題。
行番号を出力、ランダム化した後、奇数・偶数で出力方向を変更するようにしている。
ややこしいけれど、1つひとつ落ち着いて考えて実装すれば、意外と何とかなったり。
01 #!/bin/sh
02
03 echo '*****************************************************************' |
04 grep -o . |
05 awk '$0=NR' |
06 shuf |
07 awk '{printf("%s", $0%10 != 0 ? "*" : "\n*")} END{print ""}' |
08 awk 'NR%2!=0{print} NR%2==0{gsub(/./, "&\n", $0); print}' |
09 sed '/^$/d' |
10 awk -vFS= 'NF!=1{for(i=1;i<=col;i++){printf " "}; print; col+=NF} NF==1{for(i=1;i<=col;i++){printf " "} print}'
以下、コピペ実行用のワンライナーコード。
echo '*****************************************************************' | grep -o . | awk '$0=NR' | shuf | awk '{printf("%s", $0%10 != 0 ? "*" : "\n*")} END{print ""}' | awk 'NR%2!=0{print} NR%2==0{gsub(/./, "&\n", $0); print}' | sed '/^$/d' | awk -vFS= 'NF!=1{for(i=1;i<=col;i++){printf " "}; print; col+=NF} NF==1{for(i=1;i<=col;i++){printf " "} print}'
雑記
ユキヒョウに抱きつきたいにゃん。
東山で丸いものと言えば、ミュウちゃんのまんまる顔だよね。 2016.09.14(水) #東山動物園 #ユキヒョウ ミュウ pic.twitter.com/mLHp76S5ra
— ginjiro (@gin_135) 2016年9月21日
リアンちゃん。綺麗な瞳が印象的な、北国美女です。 2016.09.14(水) #東山動物園 #ユキヒョウ リアン pic.twitter.com/fkhQ64cK9d
— ginjiro (@gin_135) 2016年10月6日