2016年10月21日

シェル芸スパルタン演習(第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


シェル芸演習

問題文は、以下の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}'

雑記

  • ユキヒョウに抱きつきたいにゃん。

Tags: シェル芸 Unix
このエントリーをはてなブックマークに追加