シェル芸スパルタン演習(第18回シェル芸勉強会編)
ここ最近、Unixシェルに触る機会が減っており、CLIを使う能力の低下を感じていた。
また、近日中に、第25回シェル芸勉強会の開催が予定されており、参加するにあたり予習をしたいとも考えていた。
そこで、過去に開催されたシェル芸勉強会の問題を活用し、シェル芸力をつける演習をおこなうことにした。
今回は、問題として、第18回シェル芸勉強会の設問を用いた。
実行環境
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
usp Personal Tukubai 20160402 ( Open usp Tukubaiで代用可能)
シェル芸演習
問題文は、以下のURLから。
https://blog.ueda.asia/?p=6877
模範解答は、こちらから。
https://blog.ueda.asia/?p=6836
問1
特定の複数の値を含むキーを探す問題。
awkの連想配列を使ってキー毎に横展開した後、grepでフィルタをする。
01 #!/bin/sh
02
03 cat text |
04 sort |
05 awk '{arr[$1]=arr[$1]$2} END{for(n in arr){print n, arr[n]}}' |
06 grep 'オトン' |
07 grep 'オカン'
Tukubai版。横展開するyarrを使って、シンプルに。
01 #!/bin/sh
02
03 cat text |
04 sort |
05 yarr num=1 |
06 grep 'オトン' |
07 grep ''オカン
以下、コピペ実行用のワンライナーコード。
cat text | sort | awk '{arr[$1]=arr[$1]$2} END{for(n in arr){print n, arr[n]}}' | grep 'オトン' | grep 'オカン'
cat text | sort | yarr num=1 | grep 'オトン' | grep ''オカン
問2
2つのファイルについて、片方のみに存在するレコード、重複するレコードを、それぞれ出力する。
ファイル名をキーとして追加し、それぞれ集計すればOK。
01 #!/bin/sh
02
03 awk '{print FILENAME,$0}' a b |
04 sort -k 2,2 |
05 uniq -f 1 -c |
06 awk '$1==1{print $2,$3} 2<=$1{print "c",$3}' |
07 sort -k 1,1
別解。commコマンド、初めて使ったかも…
01 #!/bin/bash
02
03 comm <(sort a) <(sort b) |
04 sed 's/\t/t/g' |
05 sed '/^[^t]/s/^/a /; /^t[^t]/s/t/b /; /^tt/s//c /' |
06 sort
以下、コピペ実行用のワンライナーコード。
awk '{print FILENAME,$0}' a b | sort -k 2,2 | uniq -f 1 -c | awk '$1==1{print $2,$3} 2<=$1{print "c",$3}' | sort -k 1,1
comm <(sort a) <(sort b) | sed 's/\t/t/g' | sed '/^[^t]/s/^/a /; /^t[^t]/s/t/b /; /^tt/s//c /' | sort
問3
3つのファイルそれぞれについて、ファイルの中身の数値を足し合わせる問題。
これも、awkの連想配列を使えば、簡単にできる。
01 #!/bin/sh
02
03 grep -o '[0-9] \{0,1\}' {a,b,c} |
04 awk -F: '{sum[$1]+=$2} END{for(n in sum){print n,sum[n]}}'
Tukubai版。sm2を使って、ファイル名をキーとして、それぞれ集計する。
01 #!/bin/sh
02
03 grep -o '[0-9] \{0,1\}' {a,b,c} |
04 tr ':' ' ' |
05 sm2 1 1 2 2
以下、コピペ実行用のワンライナーコード。
grep -o '[0-9] \{0,1\}' {a,b,c} | awk -F: '{sum[$1]+=$2} END{for(n in sum){print n,sum[n]}}'
grep -o '[0-9] \{0,1\}' {a,b,c} | tr ':' ' ' | sm2 1 1 2 2
問4
'x’の文字がある縦軸・横軸を求める問題。
データは表形式になっているので、awkの連想配列を使って疑似多次元配列を作り、判別していく。
01 #!/bin/sh
02
03 cat cross |
04 awk -vFS= '$1=$1' |
05 sed '1!{/x/!d}' |
06 awk 'NR==1{for(i=1;i<=NF;i++){head[i]=$i}} NR!=1{printf("%s-", $1); for(i=2;i<=NF;i++){if($i=="x"){print head[i]}}}'
以下、コピペ実行用のワンライナーコード。
cat cross | awk -vFS= '$1=$1' | sed '1!{/x/!d}' | awk 'NR==1{for(i=1;i<=NF;i++){head[i]=$i}} NR!=1{printf("%s-", $1); for(i=2;i<=NF;i++){if($i=="x"){print head[i]}}}'
問5
2行以上の空行を、1行にまとめる問題。
行番号を第1フィールドに付けた後、第1フィールドをスキップしてuniqすればOK。
01 #!/bin/sh
02
03 cat text |
04 awk '$1 ~ /^..*$/{print NR,$0} $1 ~ /^$/{print}' |
05 uniq |
06 awk '{for(i=2;i<=NF;i++){printf $i" "}; print ""}'
以下、コピペ実行用のワンライナーコード。
cat text | awk '$1 ~ /^..*$/{print NR,$0} $1 ~ /^$/{print}' | uniq | awk '{for(i=2;i<=NF;i++){printf $i" "}; print ""}'
問6
チェスボード模様の画像を生成する問題。
分からなかったので、模範解答を見てしまった…
PGMなんてシンプルな画像フォーマットがあったのね。
01 #!/bin/sh
02
03 yes '0 1 0 1 0 1 0 1' |
04 head -n 8 |
05 awk 'NR%2==0{print $2,$3,$4,$5,$6,$7,$8,$1; next} 1' |
06 sed '1iP2 8 8 1' > hoge.pgm
以下、コピペ実行用のワンライナーコード。
yes '0 1 0 1 0 1 0 1' | head -n 8 | awk 'NR%2==0{print $2,$3,$4,$5,$6,$7,$8,$1; next} 1' | sed '1iP2 8 8 1' > hoge.pgm
問7
同じ文字が含まれているレコードを探す問題。
行番号を付けた後、1文字1レコードに縦展開し、sort, uniqするだけ。
01 #!/bin/sh
02
03 cat chinese_characters |
04 awk -vFS= '$1=$1' |
05 nl |
06 gawk '{for(i=2;i<=NF;i++){print $1,$i}}' |
07 LANG=C sort -k 2,2 |
08 LANG=C uniq -f 1 -D
Tukubai版。縦展開にtarrを使った。ちょっとだけシンプル。
01 #!/bin/sh
02
03 cat chinese_characters |
04 awk -vFS= '$1=$1' |
05 rank |
06 tarr num=1 |
07 LANG=C sort -k 2,2 |
08 LANG=C uniq -f 1 -D
以下、コピペ実行用のワンライナーコード。
cat chinese_characters | awk -vFS= '$1=$1' | nl | gawk '{for(i=2;i<=NF;i++){print $1,$i}}' | LANG=C sort -k 2,2 | LANG=C uniq -f 1 -D
cat chinese_characters | awk -vFS= '$1=$1' | rank | tarr num=1 | LANG=C sort -k 2,2 | LANG=C uniq -f 1 -D
問8
重複する数列のうち、最長のものを調べる問題。
良い方法が思い浮かばなかったので、愚直に全パターンを出力し、重複するものを調べた…
01 #!/bin/sh
02
03 cat number |
04 awk -vFS= '{for(i=1;i<=NF;i++){for(j=1;j<=NF;j++){for(k=j;k<=i;k++){printf $k}; print ""}}}' |
05 sed '/^$/d' |
06 sort -n |
07 uniq -d |
08 awk -vFS= '{print NF,$0}' |
09 sort -k 1,1n |
10 awk '{arr[$1]=arr[$1]" "$2} END{for(n in arr){print n,arr[n]}}' |
11 sort -k 1,1nr |
12 head -n 1
以下、コピペ実行用のワンライナーコード。
cat number | awk -vFS= '{for(i=1;i<=NF;i++){for(j=1;j<=NF;j++){for(k=j;k<=i;k++){printf $k}; print ""}}}' | sed '/^$/d' | sort -n | uniq -d | awk -vFS= '{print NF,$0}' | sort -k 1,1n | awk '{arr[$1]=arr[$1]" "$2} END{for(n in arr){print n,arr[n]}}' | sort -k 1,1nr | head -n 1
雑記
にゃーーん。
カールくんの顔を正面から見ていたら、何となくみたらし団子を連想してしまった(^_^;)
— ginjiro (@gin_135) 2016年9月18日
。oO(失敬な! ふわぁーっ。) 2016.09.14(水) #東山動物園 #カラカル カール pic.twitter.com/55UOPmBkrI