2016年10月19日

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


シェル芸演習

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

雑記

  • マヌルネコも可愛いにゃん(ΦωΦ)

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