理系学生日記

おまえはいつまで学生気分なのか

終わり行く2017年にgrepを

みんな大好き grep についてですが、かなり機能が豊富な一方でなかなかそれがまとまっていないこともあるのと、自分自身、結構細かな Tips を忘れたりするので、ここで簡単にまとめてみたいと思います。

OR 検索

後述する拡張正規表現でも可能といえば可能ですが、サクっとできるのは -e (--regexp) を複数与えることです。

$ grep -e abc -e xyz hoge.txt
abcdefg
vwxyz

この OR 条件がときどき変わって、その都度シェルスクリプトを変えたくないという場合、これらの条件は外出しすることも検討して良いでしょう。外出ししたファイルには、条件を 1 行 1 条件で並べます。 このファイルを -f (--file=filename) で与えれば、grep はそれらを OR 条件として解釈します。

$ cat cond.dat
abc
xyz

$ grep -f cond.dat hoge.txt
abcdefg
vwxyz

行全体ではなくて、特定のパターンに合致する部分だけ抽出して表示する

たとえば テキストファイルの中の IP アドレスだけ抜き出したいときとか。

具体例として、こんなゴミみたいなファイルがあるとします。 この中から IP アドレスだけ出してくれというゴミみたいな依頼がある場合があり、このような作業で時間を浪費するには人生は短すぎます。

$ cat ipaddr.txt
jdisa;eoiuraajf
dsaea
dfa;eiue127.0.0.1da324
dsai;255.255.255.240dsa;ewapa3242
324632hogesada192.168.3.0;safdsa
0.0.0.0dsaafs10.252525.0.0.dsadfsa

こういうときには、-o (--only-matching) オプションを使います。合致したところだけが標準出力に出力されるので、「検索」ではなく「抽出」に使用できます。

ちなみに、以下の例では拡張正規表現 (-E) を使っています。拡張正規表現については、別段 grep 限定の話でもないので、雰囲気で感じてもらえれば良いと思います。

$ grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}" ipaddr.txt
127.0.0.1
255.255.255.240
192.168.3.0
0.0.0.0

ちなみに、これを利用すると以下のようなことも可能になります。

grep さん、そのファイルはバイナリではなくテキストなんやで…

バイナリファイルに対して grep をかけても、grep さんは 「マッチしたよ」という情報しか教えてくれません。

$ grep . app/WEB-INF/lib/jackson-dataformat-yaml-2.8.6.jar
Binary file app/WEB-INF/lib/jackson-dataformat-yaml-2.8.6.jar matches

ホントにバイナリなら良いんですが、ときどきテキストファイルであるにも関わらず、バイナリと判定してしまうケースがあります。 この場合、テキストファイルであると、明示的に教えてあげると良いでしょう。-a (--text) オプションで指定できます。

今回のファイルは普通にバイナリファイルなので、ちょっとおかしくなっちゃいましたが。

$ grep -a . app/WEB-INF/lib/jackson-dataformat-yaml-2.8.6.jar | head -3
_tjr @&&h4ci8hv%PcON miwdMW8@ݤ- Hl&?JDi&J#rdhu߿.gQK>
\4Mh̉FMcpݎzI<b_m.k[cj49%8"bɊ<ZgBt9WWw_r*]=A8TPj"1kv''
tm"hSwa]׳/1oJ

リアルタイムに tail -fgrep したいんだけど

grep はバッファリングした後出力するのがデフォルトなので、表示するまでには若干の時間がかかります。 これをリアルタイムに行うには、--line-buffered を付与しましょう。

$ tail -f access.log | grep --line-buffered ERROR

合致する行の前後のn行を表示する

システム運用やエラー調査でよく使ったりします。特定のエラーログの直前に出力されたログの傾向から大まかな対応方針が得られることが多々あります。 前の行を num 行表示するときは -B num (--before-context=num)です。

$ grep -iB2 QRS hoge.txt
abcdefg
hijklmn
opqrstu
--
ABCDEFG
HIJKLMN
OPQRSTU

後の行を num 行表示するためには -A num (--after-context=num)、前後の num 行を表示するためには -C num (--context=num) が使用できます。 ちなみに、上記のとおり「複数箇所」がヒットする場合の区切りには -- が使用されますが、これは --group-separator オプションで変更が可能です。

 grep -i -A2 --group-separator='=====' QRS hoge.txt
opqrstu
vwxyz
ABCDEFG
=====
OPQRSTU
VWXYZ

grep に時間をかけたくない

1 件でも検索結果に合致すれば grep を打ち切りたいときがあります。 しかし、特に command | grep ... というようにパイプの後段に grep を配置する場合、command の実行が遅いと grep も終わりません。 こういうときは、合致する行が num 行になったら処理を打ち切るという -m (--max-count=num) を使います。

このあたりについては、以下のエントリが詳しいです。

netstat | grep が遅いなぁと思ったときにはgrep -m - As a Futurist...

word 検索をしたい

要するに、hoge を検索するとき、abc hoge fuga にはヒットさせたいけど、abchogefuga にはヒットさせたくない、という要件。 これは、-w (--word-regexp) オプションを使います。

どこかで書いたと思ったら、ここだった。

大文字と小文字を区別しない

このあたりは知名度が高いオプションですね。-i あるいは --ignore-case のオプション付与で実現できます。いわゆる case-insensitive な検索をしたいときに使います。

$ grep -i FgH a.txt
defghijkgrep

合致「する」行数を数える

合致した行ではなく、合致した行数だけ知りたいときには、-c あるいは --count を使います。なんかシェルのワンライナーを作るときとか、使ったりするときもあるのではないでしょうか。

$ grep -ic FgH a.txt
1

合致「しない」行を抽出する

-v あるいは --invert-matchオプションを使います。 ps -ef | grep -v grep とかのパターンでよく見かけます。もちろん、-c と組み合わせれば、合致しない行数も知ることができます。

$ grep -icv FgH a.txt
3

合致する行が「ある」ファイル名を知る

-l あるいは --files-with-matches オプションを使うことで、ファイル名のみを出力することができます。 たとえば、/etc/pam.d から password を含むファイル名を一覧するときは、以下のように使えば良いです。

$ find /etc/pam.d -type f | xargs grep -l password | head -3
/etc/pam.d/other
/etc/pam.d/passwd
/etc/pam.d/su

合致する行が「ない」ファイル名を知る

-L あるいは --files-without-match オプションを使えば良いでしょう。

$ find /etc/pam.d -type f | xargs grep -L password
/etc/pam.d/config-util
/etc/pam.d/runuser
/etc/pam.d/runuser-l

ディレクトリを grep 単体で再帰検索する

上記では、/etc/pam.d 配下のファイルを find を使って取得していますが、grep 単独でもこの再帰検索をサポートします。 多くの場合、これは -r オプション (--recursive) で実現しますが、実はこれ、--directories=recurse でも実現できます。

$ grep -rl pam_unix /etc/pam.d
/etc/pam.d/smartcard-auth
/etc/pam.d/system-auth
/etc/pam.d/fingerprint-auth
/etc/pam.d/runuser
/etc/pam.d/password-auth
$ grep -c --directories=recurse password /etc/pam.d | head -3
/etc/pam.d/other:1
/etc/pam.d/passwd:2
/etc/pam.d/su:1

また、-r ではシンボリックリンクを追ってくれませんが、-R (--dereference-recursive) を使うと、シンボリックリンクを追ってくれるようになります。使ったことないけどな。

この再帰検索において、対象から除外したいファイルやディレクトリも指定できます。ファイルの場合は --exclude=glob、ディレクトリの場合は --exclude-dir=glob という形式です。

たとえばここでは、auth で終わるファイルは除外しましょう。

$ grep -rl pam_unix /etc/pam.d
/etc/pam.d/smartcard-auth
/etc/pam.d/system-auth
/etc/pam.d/fingerprint-auth
/etc/pam.d/runuser
/etc/pam.d/password-auth

$ grep -rl pam_unix --exclude '*auth' /etc/pam.d
/etc/pam.d/runuser