2016/10/22

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


シェル芸演習

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

雑記

  • ヒョウはヒョウでも白いヒョウ、ユキヒョウ。

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