シェル芸スパルタン演習(第12回シェル芸勉強会編)
ここ最近、Unixシェルに触る機会が減っており、CLIを使う能力の低下を感じていた。
また、近日中に、第25回シェル芸勉強会の開催が予定されており、参加するにあたり予習をしたいとも考えていた。
そこで、過去に開催されたシェル芸勉強会の問題を活用し、シェル芸力をつける演習をおこなうことにした。
今回は、問題として、第12回シェル芸勉強会の設問を用いた。
実行環境
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
banner 1.3.2
curl 7.50.3
poppler 0.47.0-1
usp Personal Tukubai 20160402 ( Open usp Tukubaiで代用可能)
シェル芸演習
問題文は、以下のURLから。
http://blog.ueda.asia/?p=3569
模範解答は、こちらから。
http://blog.ueda.asia/?p=3535
問1
でっかくバッテンを書く問題。
awkで上半分を出力した後、sedで鏡合わせに出力する。
01 #!/bin/sh
02
03 echo 21 |
04 awk '{len=($1+($1%2))/2; for(i=1;i<=len;i++){print len}}' |
05 awk '{for(i=1;i<=$0*2;i++){printf (i==NR || i+NR==$0*2 ? "X" : "-")}; print ""}' |
06 sed '$!p; 1!G; $!h; $!d' |
07 tr '-' ' '
横着版。
01 #!/bin/sh
02
03 echo X |
04 banner -c X
以下、コピペ実行用のワンライナーコード。
echo 21 | awk '{len=($1+($1%2))/2; for(i=1;i<=len;i++){print len}}' | awk '{for(i=1;i<=$0*2;i++){printf (i==NR || i+NR==$0*2 ? "X" : "-")}; print ""}' | sed '$!p; 1!G; $!h; $!d' | tr '-' ' '
echo X | banner -c X
問2
回文を作る問題。
これも、sedの鏡合わせするスクリプトが、大活躍。
小問1
01 #!/bin/sh
02
03 echo たけやぶ |
04 grep -o . |
05 sed '$!p; 1!G; $!h; $!d' |
06 xargs |
07 tr -d ' '
小問2
01 #!/bin/sh
02
03 cat kaibun |
04 awk -vFS= '$1=$1' |
05 awk '{for(i=1;i<NF;i++){printf $i; arr[i]=$i}; printf $NF; for(i=NF-1;1<=i;i--){printf arr[i]}; print ""}'
以下、コピペ実行用のワンライナーコード。
echo たけやぶ | grep -o . | sed '$!p; 1!G; $!h; $!d' | xargs | tr -d ' '
cat kaibun | awk -vFS= '$1=$1' | awk '{for(i=1;i<NF;i++){printf $i; arr[i]=$i}; printf $NF; for(i=NF-1;1<=i;i--){printf arr[i]}; print ""}'
問3
南武線の駅名リストを作る問題。
適当なサイトからcurlでHTMLソースを落としてきて、整形すればOK。
01 #!/bin/sh
02
03 curl -s http://www.jreast-timetable.jp/cgi-bin/st_search.cgi?rosen=53 |
04 grep 'td class="eki"' |
05 sed 's;</\{0,1\}[^<]*>;;g'
以下、コピペ実行用のワンライナーコード。
curl -s http://www.jreast-timetable.jp/cgi-bin/st_search.cgi?rosen=53 | grep 'td class="eki"' | sed 's;</\{0,1\}[^<]*>;;g'
問4
都道府県を北から順に並べる問題。
そのままだと並べようが無いので、都道府県毎に北から順に付けられた番号、都道府県コードを利用する。
(JISで規定されている → https://ja.wikipedia.org/wiki/%E5%85%A8%E5%9B%BD%E5%9C%B0%E6%96%B9%E5%85%AC%E5%85%B1%E5%9B%A3%E4%BD%93%E3%82%B3%E3%83%BC%E3%83%89)
curlで都道府県コードを落としてきて、元ファイルと比較し、ソートする。
01 #!/bin/sh
02
03 curl -s http://www.mhlw.go.jp/topics/2007/07/dl/tp0727-1d.pdf |
04 pdftotext -layout - - |
05 sed '1,/番号/d' |
06 awk '{print $1,$2"\n"$3,$4}' |
07 awk 'NF==2' |
08 cat - pref |
09 sort |
10 sed -n '/[^0-9]$/{n;p}' |
11 sort -k 2,2n |
12 awk '$0=$1'
以下、コピペ実行用のワンライナーコード。
curl -s http://www.mhlw.go.jp/topics/2007/07/dl/tp0727-1d.pdf | pdftotext -layout - - | sed '1,/番号/d' | awk '{print $1,$2"\n"$3,$4}' | awk 'NF==2' | cat - pref | sort | sed -n '/[^0-9]$/{n;p}' | sort -k 2,2n | awk '$0=$1'
問5
各レコードについて、横方向にソートする問題。
各フィールドを行番号を付けて、一旦1レコードに展開し、ソートした後、元の形式に戻す。
01 #!/bin/sh
02
03 cat input |
04 awk '{for(i=2;i<=NF;i++){print $1,$i}}' |
05 sort -k 1,1 -k 2,2n |
06 awk '{printf ($1==o1) ? "" : "\n"$1" "; printf("%s ", $2); o1=$1}' |
07 awk '$1=$1'
Tukubai版。キーを指定して縦横展開できるtarrとyarrのお陰で、とてもシンプルに。
01 #!/bin/sh
02
03 cat input |
04 tarr num=1 |
05 sort -k 1,1 -k 2,2n |
06 yarr num=1
以下、コピペ実行用のワンライナーコード。
cat input | awk '{for(i=2;i<=NF;i++){print $1,$i}}' | sort -k 1,1 -k 2,2n | awk '{printf ($1==o1) ? "" : "\n"$1" "; printf("%s ", $2); o1=$1}' | awk '$1=$1'
cat input | tarr num=1 | sort -k 1,1 -k 2,2n | yarr num=1
問6
数値に基づいて、グラフを出力する問題。
単純に、各レコードの数値分だけ、'*'マークを出力すればOK。
01 #!/bin/sh
02
03 cat num |
04 awk '{printf $1" "; for(i=1;i<=$1;i++){printf "*"}; print ""}'
以下、コピペ実行用のワンライナーコード。
cat num | awk '{printf $1" "; for(i=1;i<=$1;i++){printf "*"}; print ""}'
問7
問6の発展系。今度は縦方向にグラフを出力する。
シェル上では、基本的に左から右へ、上から下へと、データを扱うので、その流れに逆らう方向に出力するのは、かなり難しい。
とりあえず、問6で作ったスクリプトの出力を、そのまま利用することにする。
…ええっと、これ、どうやって正答を出したんだっけorz
01 #!/bin/sh
02
03 sh q6.sh |
04 nl |
05 sort -k 2,2nr |
06 awk 'NR==1{mlen=$2; $1=$1; print} NR!=1{printf $1" "$2" "; for(i=1;i<=(mlen-$2);i++){printf "-"}; print $3}' |
07 sort -k 1,1n |
08 awk '{print $3,$2}' |
09 awk '{gsub(/./, "& ", $1); print}' |
10 awk '$1=$1' |
11 awk '{for(i=1;i<=NF;i++){arr[NR,i]=$i}} END{for(i=1;i<=NF;i++){for(j=1;j<=NR;j++){printf arr[j,i]" "}; print ""}}' |
12 tr '-' ' '
Tukubai版。…これも、何をやったのか覚えてないorz
01 #!/bin/sh
02
03 sh q6.sh |
04 rank |
05 sort -k 2,2nr |
06 awk 'NR==1{mlen=$2; print $0} NR!=1{printf $1" "$2" "; for(i=1;i<=(mlen-$2);i++){printf "-"}; print $3}' |
07 sort -k 1,1n |
08 self 3 2 |
09 gawk '{print gensub(/./, "& ", "g", $1) $2}' |
10 tateyoko |
11 tr '-' ' '
以下、コピペ実行用のワンライナーコード。
問6のコードを、シェルスクリプトとしてカレントに置いてから、実行してください。
sh q6.sh | nl | sort -k 2,2nr | awk 'NR==1{mlen=$2; $1=$1; print} NR!=1{printf $1" "$2" "; for(i=1;i<=(mlen-$2);i++){printf "-"}; print $3}' | sort -k 1,1n | awk '{print $3,$2}' | awk '{gsub(/./, "& ", $1); print}' | awk '$1=$1' | awk '{for(i=1;i<=NF;i++){arr[NR,i]=$i}} END{for(i=1;i<=NF;i++){for(j=1;j<=NR;j++){printf arr[j,i]" "}; print ""}}' | tr '-' ' '
sh q6.sh | rank | sort -k 2,2nr | awk 'NR==1{mlen=$2; print $0} NR!=1{printf $1" "$2" "; for(i=1;i<=(mlen-$2);i++){printf "-"}; print $3}' | sort -k 1,1n | self 3 2 | gawk '{print gensub(/./, "& ", "g", $1) $2}' | tateyoko | tr '-' ' '
問8
試合の結果を集計する問題。
awkの三項演算子と、連想配列を駆使して、頑張って集計する。
01 #!/bin/sh
02
03 cat result |
04 awk '{gsub(/-/, " ", $0); res=$3-$4; print (res>0) ? $1" 1 0\n"$2" 0 1" : $2" 1 0\n"$1" 0 1"}' |
05 awk 'BEGIN{printf("Team\tWin\tLose\n")} {team[$1]; res[$1,"win"]+=$2; res[$1,"lose"]+=$3} END{for(n in team){printf("%s\t%d\t%d\n",n, res[n,"win"], res[n,"lose"])}}'
以下、コピペ実行用のワンライナーコード。
cat result | awk '{gsub(/-/, " ", $0); res=$3-$4; print (res>0) ? $1" 1 0\n"$2" 0 1" : $2" 1 0\n"$1" 0 1"}' | awk 'BEGIN{printf("Team\tWin\tLose\n")} {team[$1]; res[$1,"win"]+=$2; res[$1,"lose"]+=$3} END{for(n in team){printf("%s\t%d\t%d\n",n, res[n,"win"], res[n,"lose"])}}'
雑記
マヌルネコも可愛いにゃん(ΦωΦ)
帰宅前のそわそわハニーさん。もっふる美しい。 2016.09.14(水) #東山動物園 #マヌルネコ ハニー pic.twitter.com/919du1a77a
— ginjiro (@gin_135) 2016年9月18日