「第15回ドキッ!grepだらけのシェル芸勉強会」の復習
「第15回ドキッ!grepだらけのシェル芸勉強会」が去る2月1日(日)に行われました。
残念ながらUstreamからのリモート参加となりましたが(中継Thanks)、 第11回@大阪(実は初めて参加したIT系勉強会)にも劣らない濃い世界でした。
以下、解答例と少しばかりの解説です。
なお、問題の詳細については、 【問題】第15回ドキッ!grepだらけのシェル芸勉強会 | 上田ブログ を参照してください。
Q1: 1という文字を含まないファイルを列挙
$ seq 2 5 > a
$ seq 1 9 > b
$ seq 5 11 > c
$ seq 3 6 > d
A1:
$ grep -FL '1' a b c d
grepの-Lオプション(パターンマッチ文字列を含む行が存在しないファイル名を列挙) を使用しました(-Fは「正規表現ではない単なる文字列」の意)。
$ grep -FL '1' *
上記の様に「a b c d」より「*」の方が良いかも知れません。
Q2: file.[1-9]$、file.[1-9]0$、file.[0-9]+00$のファイルのみ残す
$ seq 1 10000 | xargs -I@ touch file.@
A2:
当日のTwitter上では
$ ls file.* | grep -ve '00$' -ve '\.[1-9]0$' -ve '\.[1-9]$' | xargs rm -f
と解答しましたが、
$ ls | grep -v '^file\.\([1-9]0\?\|[0-9]\+00\)$' | xargs rm
と正規表現パターンを一つにまとめてしまう方法もあります。
Q3: テキスト内の「-v」、「-f」、「awk」の数をカウント
$ cat text1
awk -v v="hoge" 'BEGIN{print v}'
echo 'BEGIN{print 1}' | gawk -f -
nawk 'BEGIN{print " BEGIN{print x}"}' | awk -v x=3 -f -
A3:
問題を見た時は「uniq -c」が思い浮かばなかったため、解答は
$ echo -v:$(grep -ce -v text1);echo -f:$(grep -ce -f text1);echo awk:$(grep -cwe awk text1)
という今ひとつ良くない感じでした。というわけで、その反省を踏まえ
$ grep -owe '-v\|-f\|awk' text1 | sort | uniq -c
という別解を書いてみました。シェル芸としても後者の方が良いでしょう。
Q4: /etc/下の「#!/bin/sh」なファイルで「set -e」の有無を調査
当日の自分にとって、 この問題は全く歯が立たない難問でしたので「宿題」として解いてみました。
A4:
手順としては、
- ファイル内に「#!/bin/sh」があるものをリストアップ
- リスト内の各ファイルにある「set -e」の数を計上
- 最後はAWKで第2フィールドの数値で振り分け
という具合になります。
$ grep -lR '#!/bin/sh' /etc 2> /dev/null | xargs grep -c 'set -e' | awk -F: '{$2!=0?Y+=1:N+=1}END{print "有:"Y"\n無:"N}'
ちなみに、標準エラー出力を捨てておくとsudo無しで実行できるので安全かと。
Q5: 日本語やギリシャ文字のある行を除去
$ cat text2
A pen is a pen?
日本語でおk
ΩΩπ<Ω< na nandatte!!
Randy W. Bass
env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
#危険シェル芸
A5:
以下の様に
$ LANG=C grep -Pv '[^[:print:]]' text2
$ grep -Pv '[\p{Hiragana}\p{Katakana}\p{Han}\p{Greek}]' text2
正規表現パターンをPerlのものとして扱うという-Pオプションを使うのですが、 2つめのUnicodeのスクリプトを使用した解答例で、 頭に「LANG=C」を加えると1つめとは逆に間違った結果になるのが少々謎です。
Q6: ファイルの中の数字を足して10になるものを示す
$ echo 1 2 3 4 > a
$ echo 2 3 4 5 > b
$ echo 1 4 5 > c
A6:
各ファイルの「:」以降をAWKのsystem関数に喰わせる
$ grep -r . | tr ' ' '+' | awk -F: '{printf $1":";system(sprintf("echo %s | bc",$2))}' | grep ':10$'
というTwitter上における解答例の他には、grepを一切使わない
$ awk '{sum=$1+$2+$3+$4;if(sum==10){print FILENAME":"sum}}' *
という(今回のテーマからいえば邪道な)解法もあります。 なお、個人的には後者のほうが好みではありますが。
Q7: psコマンドの行、親プロセスの行、親の親のプロセスの行を表示
この問題もQ4と並ぶ難問でした (そもそも出題の意味の把握に苦労しました)。
A7:
……とはいえ、以下の当日における解答例でも間違いではないようです (少々表示が五月蝿いですが)。
$ ps axjf | grep -B2 -w 'ps' | grep -vw 'grep'
「プロセスツリーを表示すれば何とかなるのでは」と考え、manで調べてみると
プロセスツリーを表示する:
ps -ejH
ps axjf
とありましたので後者を有り難く利用した次第です。
Q8: seqとfactor以降はgrep縛りで「素数の一つ前の数で、かつ10以上の数」を列挙
$ seq 10 1000 | factor | ...(grepだけ)
A8:
シェル芸界隈ではもはやおなじみのコマンド、factorの出力フォーマットと、 grepの-Bオプション(直後の「1」が行数指定)で「マッチした行の1行前も出力する」 ことを知っていれば比較的易しい問題だと思われます。
$ seq 10 1000 | factor | grep -B1 '^[0-9]\+: [0-9]\{2,\}$' | grep '[0-9] [0-9]\+$' | grep -o '^[0-9]\+'
別解の
$ seq 10 1000 | factor | grep -B1 '^[0-9]\+: [0-9]\+$' | grep '^[0-9]\+: [0-9]\+ ' | grep -o '^[0-9]\+'
も同じようなものです。
以上、解答例と解説でした(良い復習にはなりましたが疲れた)。