シェル芸スパルタン演習(第15回シェル芸勉強会編)
ここ最近、Unixシェルに触る機会が減っており、CLIを使う能力の低下を感じていた。
また、近日中に、第25回シェル芸勉強会の開催が予定されており、参加するにあたり予習をしたいとも考えていた。
そこで、過去に開催されたシェル芸勉強会の問題を活用し、シェル芸力をつける演習をおこなうことにした。
今回は、問題として、第15回シェル芸勉強会の設問を用いた。
実行環境
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
sed (GNU sed) 4.2.2
gawk 4.1.4
usp Personal Tukubai 20160402 ( Open usp Tukubaiで代用可能)
シェル芸演習
問題文は、以下のURLから。
http://blog.ueda.asia/?p=5171
模範解答は、こちらから。
http://blog.ueda.asia/?p=5093
問1
特定の文字を含まないファイルを列挙する問題。
この回のテーマの通り、grepでファイルの中身と共に、ファイル名を出力すればOK。
01 #!/bin/sh
02
03 grep '' ./* |
04 awk -F: '{arr[$1]=arr[$1]" "$2} END{for(n in arr){print n,arr[n]}}' |
05 grep -v 1 |
06 awk '$0=$1'
以下、コピペ実行用のワンライナーコード。
grep '' ./* | awk -F: '{arr[$1]=arr[$1]" "$2} END{for(n in arr){print n,arr[n]}}' | grep -v 1 | awk '$0=$1'
問2
特定のファイルのみを残し、残りのファイルを全て削除する問題。
ファイル一覧を出力し、その中から該当するファイルのみを抽出した後、rmすれば良い。
01 #!/bin/sh
02
03 ls |
04 sed -n '/\.[1-9]$/!{/\.[1-9]0$/!{/00$/!p}}' |
05 xargs -n 1 rm
以下、コピペ実行用のワンライナーコード。
ls | sed -n '/\.[1-9]$/!{/\.[1-9]0$/!{/00$/!p}}' | xargs -n 1 rm
問3
特定の文字列の個数を数える問題。
grep一回では出来なかったので、ERE無しのみのバージョンをば…
01 #!/bin/sh
02
03 cat text1 |
04 grep -o '[^ ]*' |
05 grep -e '\-[vf]' -e '^awk$' |
06 sort |
07 uniq -c
以下、コピペ実行用のワンライナーコード。
cat text1 | grep -o '[^ ]*' | grep -e '\-[vf]' -e '^awk$' | sort | uniq -c
問4
/etc/以下から、特定の文字列を含む・含まないファイルを、それぞれ探す問題。
grepでファイル名付きで中身を出力した後、awkの連想配列で集計すれば良い。
01 #!/bin/sh
02
03 grep -r '#!/bin/sh' /etc 2>/dev/null |
04 awk -F: '$0=$1' |
05 xargs grep '.*' |
06 awk -F: '/set -e/{t[$1]++} !/set -e/{f[$1]++} END{for(n in t){true++}; for(n in f){false++}; print true,false}'
以下、コピペ実行用のワンライナーコード。
grep -r '#!/bin/sh' /etc 2>/dev/null | awk -F: '$0=$1' | xargs grep '.*' | awk -F: '/set -e/{t[$1]++; next} !/set -e/{f[$1]++} END{for(n in t){true++}; for(n in f){false++}; print true,false}'
問5
マルチバイト文字を除去する問題。
マルチバイト文字を除去するのではなく、非マルチバイト文字のみを抽出するようにすると、簡単。
01 #!/bin/sh
02
03 cat text2 |
04 LANG=C grep -v '[^[:print:]]'
以下、コピペ実行用のワンライナーコード。
cat text2 | LANG=C grep -v '[^[:print:]]'
問6
各ファイルについて、それぞれ中身の数字を足し合わせ、合計が10になるファイルを探す問題。
grepでファイル名付きで出力した後、awkでファイル名をキーとして和を求めればOK。
01 #!/bin/sh
02
03 grep '.' ./* |
04 tr ':' ' ' |
05 awk '{sum=0; for(i=2;i<=NF;i++){sum+=$i}; print $1,sum}' |
06 awk '$2==10&&$0=$1'
Tukubai版。横集計するysumを使えば、とっても楽ちん。
01 #!/bin/sh
02
03 grep '.' ./* |
04 tr ':' ' ' |
05 ysum num=1 |
06 awk '$NF==10&&$0=$1'
以下、コピペ実行用のワンライナーコード。
grep '.' ./* | tr ':' ' ' | awk '{sum=0; for(i=2;i<=NF;i++){sum+=$i}; print $1,sum}' | awk '$2==10&&$0=$1'
grep '.' ./* | tr ':' ' ' | ysum num=1 | awk '$NF==10&&$0=$1'
問7
psコマンドの出力から、psコマンド、その親、親の親プロセスを出力する問題。
実はこの問題で、初めてpsの-eoオプションの存在を知ったり…
表示されるPIDは数値の昇順になっている事と、親PIDは子PIDより小さい値になる事を利用して、上手いこと抽出する。
01 #!/bin/sh
02
03 ps -eo pid,ppid,command |
04 tac |
05 awk '/[p]s -eo pid,ppid,command/{print} !/[p]s -eo pid,ppid,command/{arr[NR]=$0} END{for(n in arr){print arr[n]}}' |
06 sed '/PID *PPID *COMMAND/d' |
07 awk 'NR==1{print; ppid=$2} $1==ppid{print; ppid=$2}'
以下、コピペ実行用のワンライナーコード。
ps -eo pid,ppid,command | tac | awk '/[p]s -eo pid,ppid,command/{print} !/[p]s -eo pid,ppid,command/{arr[NR]=$0} END{for(n in arr){print arr[n]}}' | sed '/PID *PPID *COMMAND/d' | awk 'NR==1{print; ppid=$2} $1==ppid{print; ppid=$2}'
問8
ドキッ★grepだらけの問題♥
…じゃなくて、素数の1つ前 かつ 10以上の数を求める問題(疲れてる)。
ポイントは、grepの-Bオプション。
このオプションを使えば、マッチしたレコードの1つ前(素数の1つ前)を抽出できる。
後は、条件に一致するように、フィルタするだけ。
01 #!/bin/sh
02
03 seq 10 1000 |
04 factor |
05 grep -B 1 '^[1-9][0-9]*: [1-9][0-9]*$' |
06 grep -v '^[1-9][0-9]*: [1-9][0-9]*$' |
07 grep -v '^[1-9]:' |
08 grep -v '[-]' |
09 grep -o '\([1-9][0-9]*\):' |
10 grep -o '[1-9][0-9]*'
以下、コピペ実行用のワンライナーコード。
seq 10 1000 | factor | grep -B 1 '^[1-9][0-9]*: [1-9][0-9]*$' | grep -v '^[1-9][0-9]*: [1-9][0-9]*$' | grep -v '^[1-9]:' | grep -v '[-]' | grep -o '\([1-9][0-9]*\):' | grep -o '[1-9][0-9]*'
雑記
ヒョウはヒョウでも白いヒョウ、ユキヒョウ。