grep
シェル
シェル芸

【シェル芸人への道】grepを訪ねて

そのコマンド、grep

Linuxのお勉強、CUIの使い方を勉強する際、かなり初期に出てくるであろう grep
私は ls, cd に次いで3番目のコマンドとして grep を学んだ記憶があります。

日頃からお世話になっている grep ですが、今日は改めてこのコマンドの奥深さを学んでみたいと思います。
環境はUbuntu 16.04にくっついてた GNU grep 2.25 です。

バージョン
root@ubuntu16:~# cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"

root@ubuntu16:~# grep --version
grep (GNU grep) 2.25
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Mike Haertel and others, see <http://git.sv.gnu.org/cgit/grep.git/tree/AUTHORS>.

grepとは

さて、まずは grep とは一体何かを改めて調べてみます。
Wikipediaによれば、

grep 【名詞】
UNIXおよびUnixオペレーティングシステムにおけるコマンド。テキストファイル中から、正規表現に一致する行を検索して出力する。

と、まぁそうだよねというようなことが書いてありますが、その少し後に

grep の名の由来は、ラインエディタedのコマンド g/re/p であり、その意味するところは「ファイル全体から/正規表現に一致する行を/表示する」である。

ということが書いてあります。
「ググる」と同じように「 grep する」なんて言葉が普通に通じてしまいますが、元々の由来はこういうことだったようです。
UNIX界隈では置換などの際に g (global) やら p (print) やら、また一緒に正規表現 re(regular expression) を使ったりしますが、その所作をまさしく表したコマンド名と言えます。
たいへん理にかなったコマンド名ですね。

grepの一族

さて、人間生きていると grep そのものの他に egrep やら fgrep など、よく似た名前のコマンドを見たり、聞いたりすることがあります。
一体どんなものがあるのでしょうか。調べてみましょう。

root@ubuntu16:~# compgen -c | sort | uniq | grep 'grep'
bzegrep
bzfgrep
bzgrep
egrep
fgrep
grep
lzegrep
lzfgrep
lzgrep
pgrep
ptargrep
rgrep
xzegrep
xzfgrep
xzgrep
zegrep
zfgrep
zgrep

root@ubuntu16:~# compgen -c | sort | uniq | grep 'grep' | wc -l
18

なんということでしょう、18個も grep を名乗るコマンドがあるではありませんか。
しかし大変なのはそこではありません。

grep を調べるために grep を使ってしまっている」

そう、これです。なんて罪深いのでしょう。
シェル芸人たるもの別解を探さねばなりません。やってみましょう。

awkでgrep(awkrep)
root@ubuntu16:~# compgen -c | sort | uniq | awk '/grep/{print}' | wc -l
18
sedでgrep(sedrep)
root@ubuntu16:~# compgen -c | sort | uniq | sed -n '/grep/p' | wc -l
18
powerでgrep(powerep)
root@ubuntu16:~# compgen -c | sort | uniq | while read LINE ; do if [[ "${LINE}" =~ grep ]] ; then echo ${LINE} ; fi ; done | wc -l
18

とりあえず3つあれば大丈夫(根拠不明)でしょう...。

一族を並べてみる

いっぱいあるので並べかえてみましょう。

root@ubuntu16:~# compgen -c | sort | uniq | grep 'grep'
bzegrep
bzfgrep
bzgrep
egrep
fgrep
grep
lzegrep
lzfgrep
lzgrep
pgrep
ptargrep
rgrep
xzegrep
xzfgrep
xzgrep
zegrep
zfgrep
zgrep

たしかに並んではいるのですが、文字数が色々なのでイマイチよくわかりません。
ですので、「文字数」を「辞書式」に優先して並べてみましょう。

root@ubuntu16:~# for CMD in $(compgen -c | sort | uniq | grep 'grep') ; do echo ${#CMD}: ${CMD} ; done | sort
4: grep
5: egrep
5: fgrep
5: pgrep
5: rgrep
5: zgrep
6: bzgrep
6: lzgrep
6: xzgrep
6: zegrep
6: zfgrep
7: bzegrep
7: bzfgrep
7: lzegrep
7: lzfgrep
7: xzegrep
7: xzfgrep
8: ptargrep

${#変数名} で変数に入っている 文字数のカウント ができる。たいへんに便利ですね。

基本形

さて、こいつらをよく見てみると基本的には「 ○○のgrep 」みたいなネーミングになっています。
まずは基本となる grep に一文字を加えたものを見てみましょう。

コマンド名 由来 意味 grepでの相当オプション 備考
grep - grep - 基本形。
egrep extended-regexp grep 拡張正規表現に対応したgrep -E
fgrep fixed-strings grep 固定文字列のみに対応したgrep -F faster grepかと思っていた時期が私にもありました。
pgrep process grep プロセス一覧に対するgrep - grep -P と同じだと思った?別物だよ。
rgrep recursive grep 再帰的なgrep -r grep -R はまたちょっと違う。

派生形

上記のもの、特に grep, egrep, fgrep の派生形となっているのが以下です。
基本的には圧縮ファイルのままにgrepできるというものですね。

接頭辞 由来 意味 対象 備考
z gzip gzip圧縮ファイルに対するgrep zgrep, zegrep, zfgrep grep -z はまた別物。ごくまれに使う。
bz bzip2 bzip2圧縮ファイルに対するgrep bzgrep, bzegrep, bzfgrep 使ったことがない。
lz lzma lzma圧縮ファイルに対するgrep lzgrep, lzegrep, lzfgrep 使ったことがない。
xz xz xz圧縮ファイルに対するgrep xzgrep, xzegrep, xzfgrep 使ったことがない。
ptar perl-regexp tar tar圧縮ファイルに対するperl正規表現でのgrep ptargrep pgrep は他人です。

実践grep

それでは実践です。
実践にあたっては適当なテキストファイル、そう、教材が必要です。
以下のサイトに敬礼しつつ、日本語と英語で好きな教材を探しましょう。

教材の準備

今回は「 こころ(夏目漱石) 」と「 不思議の国のアリス(ルイス・キャロル) 」を教材に選んでみます。

root@ubuntu16:~# curl -s -o kokoro.zip http://www.aozora.gr.jp/cards/000148/files/773_ruby_5968.zip
root@ubuntu16:~# curl -s -o alice.zip http://www.gutenberg.org/files/35688/35688.zip
root@ubuntu16:~# ls -lh
total 196K
-rw-r--r--  1 root root  39K Jul  9 16:00 alice.zip
-rw-r--r--  1 root root 151K Jul  9 15:59 kokoro.zip

それでは解凍しましょう。 unzip がなかったら apt-get install unzip でもしてください。

root@ubuntu16:~# unzip kokoro.zip
Archive:  kokoro.zip
Made with MacWinZipper?
  inflating: kokoro.txt

root@ubuntu16:~# unzip alice.zip
Archive:  alice.zip
  inflating: 35688.txt

# ファイル名を変えておく
root@ubuntu16:~# mv 35688.txt alice.txt

この時点でこころのファイルに一抹の不安が頭をよぎります。
おそるおそるファイルの中身を確認してみましょう。

root@ubuntu16:~# head kokoro.txt
アア・
        ??

-------------------------------------------------------
yeLXg?サ・
??「トz    L

stF
   r
i疔s?ュオtヘ

bF
  r?tュカn?頏R?・
                L

root@ubuntu16:~# head alice.txt
The Project Gutenberg EBook of Alice in Wonderland, by Alice Gerstenberg

This eBook is for the use of anyone anywhere at no cost and with
almost no restrictions whatsoever.  You may copy it, give it away or
re-use it under the terms of the Project Gutenberg License included
with this eBook or online at www.gutenberg.net


Title: Alice in Wonderland
       A Dramatization of Lewis Carroll's "Alice's Adventures in

ふぅ、奴が出たようですね。念のため確認してみましょう。たすけて nkf えもん。

root@ubuntu16:~# apt-get install nkf

root@ubuntu16:~# nkf -g kokoro.txt
Shift_JIS

予想通りですね。彼には生まれ変わってもらいましょう。
nkf -wworld-wide なUTF-8への変換ができます。

root@ubuntu16:~# nkf -w --overwrite kokoro.txt

root@ubuntu16:~# head kokoro.txt
こころ
夏目漱石

-------------------------------------------------------
【テキスト中に現れる記号について】

《》:ルビ
(例)私《わたくし》は

|:ルビの付く文字列の始まりを特定する記号

root@ubuntu16:~# nkf -g kokoro.txt
UTF-8

これで教材の準備が整いました。
あとは順にオプションを調べていってみましょう。

どれくらいくらいあるのかというと...

root@ubuntu16:~# grep --help | head
Usage: grep [OPTION]... PATTERN [FILE]...
Search for PATTERN in each FILE or standard input.
PATTERN is, by default, a basic regular expression (BRE).
Example: grep -i 'hello world' menu.h main.c

Regexp selection and interpretation:
  -E, --extended-regexp     PATTERN is an extended regular expression (ERE)
  -F, --fixed-strings       PATTERN is a set of newline-separated strings
  -G, --basic-regexp        PATTERN is a basic regular expression (BRE)
  -P, --perl-regexp         PATTERN is a Perl regular expression

root@ubuntu16:~# grep --help | grep -E "^ +-[a-Z][, ]" | wc -l
36

中々手強いですね。まずは不思議の国のアリスを題材に、各オプションで素直に遊んでみましょう。

普通の使い方

まずはひねりもなく普通に使ってみましょう。 play に関連する部分をひたすら探してみます。

基本マッチ

普通のgrep
root@ubuntu16:~# grep "play" alice.txt
  Rights to produce this play in all countries of the world
The illustrations of the characters of the play in this book were drawn by
W. H. Gilmore staged the play with the following cast:
()

マッチ行番号表示(-n, number)

それぞれ何行目に出てきているのか気になりますね。 -n を使ってみましょう。

行数の表示(-n)
root@ubuntu16:~# grep -n "play" alice.txt
72:  Rights to produce this play in all countries of the world
99:The illustrations of the characters of the play in this book were drawn by
102:W. H. Gilmore staged the play with the following cast:
()

表示個数制限(-m, max)

ちょっと数が多いので最初の5個だけを見たくなりました。 -m の出番です。

表示個数の制限(-m)
root@ubuntu16:~# grep -n -m 5 "play" alice.txt
72:  Rights to produce this play in all countries of the world
99:The illustrations of the characters of the play in this book were drawn by
102:W. H. Gilmore staged the play with the following cast:
173:_ALICE'S home. LEWIS CARROLL is discovered, playing chess. Golden-haired
256:Why do people always play with kings and queens? Mother has them in her

個数カウント(-c, count)

結局何個ヒットしているのでしょうか。 -c の出番です。

個数の表示(-c)
root@ubuntu16:~# grep -c "play" alice.txt
29

先頭が大文字の "Play" はどうでしょう。

root@ubuntu16:~# grep -c "Play" alice.txt
1

大小文字の無視(-i, ignore)

とにかく "play" 的なものをカウントしたいです。大文字小文字を無視しましょう。 -i の出番です。

大小文字のマッチ(-i)
# 87行目の "Players" が一緒にヒットするようになった
root@ubuntu16:~# grep -n -i "play" alice.txt
72:  Rights to produce this play in all countries of the world
87:Chicago, was produced by The Players Producing Company of Chicago (Aline
99:The illustrations of the characters of the play in this book were drawn by
102:W. H. Gilmore staged the play with the following cast:

root@ubuntu16:~# grep -c -i "play" alice.txt
30

単語マッチ(-w, word)

よく見ると「 dis play 」なんてのもヒットしているようです。純粋な "play" をカウントしたいので -w の出番です。

単語単位でのマッチ(-w)
root@ubuntu16:~# grep -n -i -w "play" alice.txt
72:  Rights to produce this play in all countries of the world
99:The illustrations of the characters of the play in this book were drawn by
102:W. H. Gilmore staged the play with the following cast:

root@ubuntu16:~# grep -n -i -w -c "play" alice.txt
12

除外(-v, invert)

結構減りました。ヒットしなくなった18行を探しましょう。 -v の出番です。

除外(-v)
root@ubuntu16:~# grep -i -w -v "play" alice.txt | grep -i "play"
Chicago, was produced by The Players Producing Company of Chicago (Aline
_ALICE'S home. LEWIS CARROLL is discovered, playing chess. Golden-haired
playing cards too. Look!
[_ALICE goes to the mantel and takes a pack of playing cards from the
You're playing against yourself, aren't you?
(略)

root@ubuntu16:~# grep -i -w -v "play" alice.txt | grep -c -i "play"
18

日本語でgrep

さて、続いては日本語の「こころ」を使ってgrepの練習をしてみましょう。
ふしぎの国のアリスではそのままやっていましたが、本文のほかにヘッダーとフッターがついているのでファイルを少し加工してみます。

前準備

ヘッダー
root@ubuntu16:~# cat -n kokoro.txt | head -n 30
     1  こころ
     2  夏目漱石
     3
     4  -------------------------------------------------------
     5  【テキスト中に現れる記号について】
()
    23   私《わたくし》はその人を常に先生と呼んでいた。だからここでもただ先生と書くだけで本名は打ち明けない。これは世間を憚《はば》かる遠慮というよりも、その方が私にとって自然だからである。私はその人の記憶を呼び起すごとに、すぐ「先生」といいたくなる。筆を執《と》っても心持は同じ事である。よそよそしい頭文字《かしらもじ》などはとても使う気にならない。
()

このように、ヘッダがあるため本文は23行目から始まっているようです。
一方でフッターはどうでしょう。

フッター
root@ubuntu16:~# cat -n kokoro.txt | tail -n 20
()
  1579   私は私の過去を善悪ともに他《ひと》の参考に供するつもりです。しかし妻だけはたった一人の例外だと承知して下さい。私は妻には何にも知らせたくないのです。妻が己《おの》れの過去に対してもつ記憶を、なるべく純白に保存しておいてやりたいのが私の唯一《ゆいいつ》の希望なのですから、私が死んだ後《あと》でも、妻が生きている以上は、あなた限りに打ち明けられた私の秘密として、すべてを腹の中にしまっておいて下さい。」
()
  1594  青空文庫作成ファイル:
  1595  このファイルは、インターネットの図書館、青空文庫(http://www.aozora.gr.jp/)で作られました。入力、校正、制作にあたったのは、ボランティアの皆さんです。

本文の終わりは1579行のようですね。それではこの範囲を抜き出してみましょう。

root@ubuntu16:~# sed -n 23,1579p kokoro.txt > kokoro_main.txt
root@ubuntu16:~# head -n 1 kokoro_main.txt ; echo "#####" ; tail -n 1 kokoro_main.txt
 私《わたくし》はその人を常に先生と呼んでいた。だからここでもただ先生と書くだけで本名は打ち明けない。これは世間を憚《はば》かる遠慮というよりも、その方が私にとって自然だからである。私はその人の記憶を呼び起すごとに、すぐ「先生」といいたくなる。筆を執《と》っても心持は同じ事である。よそよそしい頭文字《かしらもじ》などはとても使う気にならない。
#####
 私は私の過去を善悪ともに他《ひと》の参考に供するつもりです。しかし妻だけはたった一人の例外だと承知して下さい。私は妻には何にも知らせたくないのです。妻が己《おの》れの過去に対してもつ記憶を、なるべく純白に保存しておいてやりたいのが私の唯一《ゆいいつ》の希望なのですから、私が死んだ後《あと》でも、妻が生きている以上は、あなた限りに打ち明けられた私の秘密として、すべてを腹の中にしまっておいて下さい。」

よさそうですね。
あともうひとつ、読む文には大変助かるのですが《》で書かれているルビを除去しましょう。

root@ubuntu16:~# sed 's/《.*》//g' kokoro_main.txt | head
 私などはとても使う気にならない。
 私が先生と知り合いになったのは鎌倉より帰るべきはずであった。それで彼はとうとう帰る事になった。せっかく来た私は一人取り残された。
 学校の授業が始まるにはまだ大分な宿を探す面倒ももたなかったのである。
 宿は鎌倉でも辺鄙を一つ越さなければ手が届かなかった。車で行っても二十銭は取られた。けれども個人の別荘はそこここにいくつでも建てられていた。それに海へはごく近いので海水浴をやるには至極便利な地位を占めていた。
 私は毎日海へはいりに出掛けた。古い燻るのは愉快であった。
 私は実に先生をこの雑沓てる事にしていた。

[#5字下げ]二[#「二」は中見出し]

 私れていたからである。

んんん?どうもおかしいですね。
よく見てみると以下のような除去が起こっているようです。

 私《わたくし》はその人を常に先生と呼んでいた。だからここでもただ先生と書くだけで本名は打ち明けない。これは世間を憚《はば》かる遠慮というよりも、その方が私にとって自然だからである。私はその人の記憶を呼び起すごとに、すぐ「先生」といいたくなる。筆を執《と》っても心持は同じ事である。よそよそしい頭文字《かしらもじ》などはとても使う気にならない。

 ↓

 私などはとても使う気にならない。

確かにsedでは「《~》までを除去する」と指定したものの、ちょっと意図とはずれてしまいました。
このようにマッチしすぎることがあるので、 最長一致 には注意しておきましょう。

気を取り直して、以下のようにすれば最長一致に惑わされることなく、望みの除去を行うことができます。

root@ubuntu16:~# cat kokoro_main.txt | sed 's/《[^》]*》//g' | head
 私はその人を常に先生と呼んでいた。だからここでもただ先生と書くだけで本名は打ち明けない。これは世間を憚かる遠慮というよりも、その方が私にとって自然だからである。私はその人の記憶を呼び起すごとに、すぐ「先生」といいたくなる。筆を執っても心持は同じ事である。よそよそしい頭文字などはとても使う気にならない。
()

二重括弧の中について「 任意の1文字 ( . )」でマッチングさせていたところを「 》以外 ( [^》] )」でマッチングしているのがポイントです。

root@ubuntu16:~# sed -n 23,1579p kokoro.txt | sed 's/《[^》]*》//g' > kokoro_main.txt

基本マッチ

それではあの有名なフレーズを探すべく「 罪悪 」でgrepしてみましょう。

root@ubuntu16:~# grep -n "罪悪" kokoro_main.txt
202:「聞こえました。恋の満足を味わっている人はもっと暖かい声を出すものです。しかし……しかし君、恋は罪悪ですよ。解っていますか」
()

出ました。こんなところにあったんですね。もっと後ろのイメージがありました。

前後行の表示(-B/A, Before/After)

ちょっと前後の文脈が気になりますね。前後3行ずつ見てみましょう。 -B-A の出番です。

マッチ前後を確認(-A/B)
root@ubuntu16:~# grep -n -B 3 -A 3 "しかし君、恋は罪悪ですよ" kokoro_main.txt
199-「ええ」
200-「君は今あの男と女を見て、冷評しましたね。あの冷評のうちには君が恋を求めながら相手を得られないという不快の声が交っていましょう」
201-「そんな風に聞こえましたか」
202:「聞こえました。恋の満足を味わっている人はもっと暖かい声を出すものです。しかし……しかし君、恋は罪悪です  。解っていますか」
203- 私は急に驚かされた。何とも返事をしなかった。
204-
205-[#5字下げ]十三[#「十三」は中見出し]

残念ながらシチュエーションはさっぱりわかりませんね。そういうこともあるでしょう。(楽観的)

"漢字クラス" のマッチ

ついでなので「楽観的」を調べてみましょう。

root@ubuntu16:~# grep -n "楽観的" kokoro_main.txt
970: 兄と前後して着いた妹の夫の意見は、我々よりもよほど楽観的であった。父は彼に向かって妹の事をあれこれと尋ねていた。「身体が身体だからむやみに汽車になんぞ乗って揺れない方が好い。無理をして見舞に来られたりすると、かえってこっちが心配だから」といっていた。「なに今に治ったら赤ん坊の顔でも見に、久しぶりにこっちから出掛けるから差支えない」ともいっていた。

ありますね。他に ○○的 はあるんでしょうか。

root@ubuntu16:~# grep -c -E "..的" kokoro_main.txt
76

結構ありますね。具体的に見てみましょう。

root@ubuntu16:~# grep -n -m 3 -E "..的" kokoro_main.txt
13: 私は単に好奇心のために、並んで浜辺を下りて行く二人の後姿を見守っていた。すると彼らは真直に波の中に足を踏 み込んだ。そうして遠浅の磯近くにわいわい騒いでいる多人数の間を通り抜けて、比較的広々した所へ来ると、二人とも泳ぎ出した。彼らの頭が小さく見えるまで沖の方へ向いて行った。それから引き返してまた一直線に浜辺まで戻って来た。掛茶屋へ帰ると、井戸の水も浴びずに、すぐ身体を拭いて着物を着て、さっさとどこへか行ってしまった。
15: その時の私は屈托がないというよりむしろ無聊に苦しんでいた。それで翌日もまた先生に会った時刻を見計らって、 わざわざ掛茶屋まで出かけてみた。すると西洋人は来ないで先生一人|麦藁帽を被ってやって来た。先生は眼鏡をとって台の上に置いて、すぐ手拭で頭を包んで、すたすた浜を下りて行った。先生が昨日のように騒がしい浴客の中を通り抜けて、一人で泳ぎ出した時、私は急にその後が追い掛けたくなった。私は浅い水を頭の上まで跳かして相当の深さの所まで来て、そこから先生を目標に抜手を切った。すると先生は昨日と違って、一種の弧線を描いて、妙な方向から岸の方へ帰り始めた。それで私の目的はついに達せられなかった。私が陸へ上がって雫の垂れる手を振りながら掛茶屋に入ると、先生はもうちゃんと着物を着て入れ違いに外へ出て行った。
19: 私は次の日も同じ時刻に浜へ行って先生の顔を見た。その次の日にもまた同じ事を繰り返した。けれども物をいい掛 ける機会も、挨拶をする場合も、二人の間には起らなかった。その上先生の態度はむしろ非社交的であった。一定の時刻に超然として来て、また超然と帰って行った。周囲がいくら賑やかでも、それにはほとんど注意を払う様子が見えなかった。最初いっしょに来た西洋人はその後まるで姿を見せなかった。先生はいつでも一人であった。

これはいけません。15行のマッチを見てみると、 の目的 というところでヒットしています。
確かに横着してピリオドでマッチさせたのがよくなかったようです。漢字でマッチさせましょう、漢字で。

...はて、漢字の文字クラスなんてあったでしょうか。

ありました。 細かな点を抜きにすれば [一-龠] を漢字の文字クラスとして使えるようです。早速使ってみます。

root@ubuntu16:~# grep -n -E "[一-龠]{2}的" kokoro_main.txt | wc -l
56

root@ubuntu16:~# grep -n -m 3 -E "[一-龠]{2}的" kokoro_main.txt
13: 私は単に好奇心のために、並んで浜辺を下りて行く二人の後姿を見守っていた。すると彼らは真直に波の中に足を踏 み込んだ。そうして遠浅の磯近くにわいわい騒いでいる多人数の間を通り抜けて、比較的広々した所へ来ると、二人とも泳ぎ出した。彼らの頭が小さく見えるまで沖の方へ向いて行った。それから引き返してまた一直線に浜辺まで戻って来た。掛茶屋へ帰ると、井戸の水も浴びずに、すぐ身体を拭いて着物を着て、さっさとどこへか行ってしまった。
19: 私は次の日も同じ時刻に浜へ行って先生の顔を見た。その次の日にもまた同じ事を繰り返した。けれども物をいい掛 ける機会も、挨拶をする場合も、二人の間には起らなかった。その上先生の態度はむしろ非社交的であった。一定の時刻に超然として来て、また超然と帰って行った。周囲がいくら賑やかでも、それにはほとんど注意を払う様子が見えなかった。最初いっしょに来た西洋人はその後まるで姿を見せなかった。先生はいつでも一人であった。
22: しばらくして海の中で起き上がるように姿勢を改めた先生は、「もう帰りませんか」といって私を促した。比較的強 い体質をもった私は、もっと海の中で遊んでいたかった。しかし先生から誘われた時、私はすぐ「ええ帰りましょう」と快く答えた。そうして二人でまた元の路を浜辺へ引き返した。

これで望みのマッチができました。多分。

日本語の単語マッチ(mecab & -w)

さて、では最後に単語単位のマッチを日本語でもやってみたいと思います。「比較」と「比較的」を区別してみましょう。

root@ubuntu16:~# grep -n -w "比較" kokoro_main.txt
(出力なし)

あれ?何も出ませんね。「比較」ってないんでしょうか。

root@ubuntu16:~# grep -n "比較" kokoro_main.txt
()
433: 私は心のうちで、父と先生とを比較して見た。両方とも世間から見れば、生きているか死んでいるか分らないほど大人しい男であった。他に認められるという点からいえばどっちも零であった。それでいて、この将碁を差したがる父は、単なる娯楽の相手としても私には物足りなかった。かつて遊興のために往来をした覚えのない先生は、歓楽の交際から出る親しみ以上に、いつか私の頭に影響を与えていた。ただ頭というのはあまりに冷やか過ぎるから、私は胸といい直したい。肉のなかに先生の力が喰い込んでいるといっても、血のなかに先生の命が流れているといっても、その時の私には少しも誇張でないように思われた。私は父が私の本当の父であり、先生はまたいうまでもなく、あかの他人であるという明白な事実を、ことさらに眼の前に並べてみて、始めて大きな真理でも発見したかのごとくに驚いた。
()

ありました。ではなぜこれがマッチしなかったんでしょうか。
単語単位のマッチを行う -w オプションでは、単語の認識をスペースで行っています。アルファベット言語だと単語の区切りにスペースが入りますから、単語認識のルールとしてはぴったりです。
一方日本語ではどうでしょう。スペースは入りませんね。
そのため、句点や読点を挟んだとしても、改行やスペースが入らなければ 行全体が1つの単語 とみなされてしまいます。
grep自体は英語圏で生まれたものですから、このあたりは致し方ない部分であると言えます。

しかし、そうは言っても単語マッチがやりたいので頑張りましょう。
日本語解析の強い味方、 mecab を使います。

root@ubuntu16:~# apt-get install mecab

インストールはこれだけです。すばらしい。試してみましょう。

root@ubuntu16:~# echo "シェル芸人" | mecab
� ニテシ�,オュケ�,*,*,*,*,*
キ�  ?サ�,ソヘフセ,*,*,*,*,*
ァ罩�
ク ニテシ�,オュケ�,*,*,*,*,*
莠 ?サ�,ソヘフセ,*,*,*,*,*
コ ニテシ�,オュケ�,*,*,*,*,*
EOS

洗礼がひどいですね。調べてみるとUTF-8用の辞書がないのがダメらしいです。

root@ubuntu16:~# apt-get install mecab-ipadic-utf8

root@ubuntu16:~# echo "シェル芸人" | mecab 
シェル   名詞,一般,*,*,*,*,シェル,シェル,シェル
芸人  名詞,一般,*,*,*,*,芸人,ゲイニン,ゲイニン
EOS

すばらしいですね。ではこいつをどうやって日本語の単語マッチに使うかというと -O wakati オプションを使います。
分かち書きといって、元の行を形態素に分割した状態で出力するものです。

root@ubuntu16:~# echo "本日はシェル芸人なり" | mecab -O wakati
本日 は シェル 芸人 なり

こんな感じ。あとはこいつを通したあとにgrepをかけてやると...。

root@ubuntu16:~# mecab -O wakati kokoro_main.txt | grep -n -m 3 -w "比較"
433:  私 は 心 の うち で 、 父 と 先生 と を 比較 し て 見 た 。 両方 とも 世間 から 見れ ば 、 生き て いる か 死ん で いる か 分ら ない ほど 大人しい 男 で あっ た 。 他 に 認め られる という 点 から いえ ば どっち も 零 で あっ た 。 それでいて 、 この 将 碁 を 差し た がる 父 は 、 単なる 娯楽 の 相手 として も 私 に は 物足りなかっ た 。 かつて 遊興 の ため に 往来 を し た 覚え の ない 先生 は 、 歓楽 の 交際 から 出る 親しみ 以上 に 、 いつ か 私 の 頭 に 影響 を 与え て い た 。 ただ 頭 という の は あまりに 冷やか 過ぎる から 、 私 は 胸 と いい 直し たい 。 肉 の なか に 先生 の 力 が 喰い 込ん で いる と いっ て も 、 血 の なか に 先生 の 命 が 流れ て いる  と いっ て も 、 その 時 の 私 に は 少し も 誇張 で ない よう に 思わ れ た 。 私 は 父 が 私 の 本当 の 父 で あり 、 先生 は また いう まで も なく 、 あか の 他人 で ある という 明白 な 事実 を 、 ことさら に 眼 の 前 に 並   て み て 、 始めて 大きな 真理 で も 発見 し た か の ご とく に 驚い た 。
750:  父 は この 言葉 を 何 遍 も 繰り返し た 。 私 は 心 の うち で この 父 の 喜び と 、 卒業 式 の あっ た 晩 先生 の 家 の 食卓 で 、 「 お 目 出 とう 」 と いわ れ た 時 の 先生 の 顔 付 と を 比較 し た 。 私 に は 口 で 祝っ て くれ ながら 、 腹 の 底 で けなし て いる 先生 の 方 が 、 それほど に も ない もの を 珍し そう に 嬉し がる 父 より も 、 かえって 高尚 に 見え た 。 私 は しまいに 父 の 無知 から 出る 田舎 臭い ところ に 不快 を 感   出し た 。
896:  私 の 哀愁 は この 夏 帰省 し た 以後 次第に 情調 を 変え て 来 た 。 油蝉 の 声 が つくつく法師 の 声 に 変る ごとく に 、 私 を 取り巻く 人 の 運命 が 、 大きな 輪廻 の うち に 、 そろそろ 動い て いる よう に 思わ れ た 。 私 は 淋し そう な 父 の 態度 と 言葉 を 繰り返し ながら 、 手紙 を 出し て も 返事 を 寄こさ ない 先生 の 事 を また 憶 い 浮べ た 。 先生 と 父 と は 、 まるで 反対 の 印象 を 私 に 与える 点 において 、 比較 の 上 に も 、 連想 の 上 に も 、 いっしょ に 私 の 頭 に 上り やすかっ た 。

完成です。

ちなみにこんなのもあるようです。

ただオリジナルのgrepを拡張したものでないため、 -v 以外のオプションが使えません。
全体的な使い勝手に難があるため、オリジナルのgrepが使える形で進めました。

grepの速さを知る

grepって速いらしいです。
冒頭に使ったなんちゃってgrepとオリジナルのgrepで速度比較をしてみましょう。

  • grep
  • sedrep
  • awkrep
  • powerep

出来る限り大きなテキストファイルが欲しいのですが、そうそうデカイテキストファイルなんか見つからないのでアリスをひたすら連結してみます。

root@ubuntu16:~# for i in {1..1000} ; do  cat alice.txt >> big_alice.txt ; done
root@ubuntu16:~# for i in {1..10000} ; do  cat alice.txt >> huge_alice.txt ; done

root@ubuntu16:~# ls -lh *alice.txt
-rw-r--r-- 1 root root 108K Mar 26  2011 alice.txt
-rw-r--r-- 1 root root 105M Oct  1 06:38 big_alice.txt
-rw-r--r-- 1 root root 1.1G Oct  1 06:40 huge_alice.txt

ひたすら "play" をカウントさせるだけの簡単なお仕事です。まずは100MB程度の big_alice.txt から。

# grep氏
root@ubuntu16:~# time cat big_alice.txt | grep "play" | wc -l
29000

real    0m0.106s
user    0m0.060s
sys     0m0.048s

# sedrep氏
root@ubuntu16:~# time cat big_alice.txt | sed -n '/play/p' | wc -l
29000

real    0m1.066s
user    0m0.996s
sys     0m0.068s

# awkrep氏
root@ubuntu16:~# time cat big_alice.txt | awk '/play/{print}' | wc -l
29000

real    0m1.267s
user    0m0.628s
sys     0m0.192s

# powerep氏
root@ubuntu16:~# time cat big_alice.txt | while read LINE ; do if [[ "${LINE}" =~ play ]] ; then echo ${LINE} ; fi ; done | wc -l
29000

real    0m55.960s
user    0m39.788s
sys     0m16.144s

結果はすべて正しく出ているようですが、すでに圧倒的な戦力差を見せつけるgrep氏。
予想通りではありますが、powerep氏はここでリタイアですね。

実力を見るために複数回まわしてみましょう。ワンライナーawkで平均、最大、最小を出します。

# grep氏
root@ubuntu16:~# for i in {1..10} ; do ( time cat big_alice.txt | grep "play" | wc -l) 2>&1 | grep real | awk '{print $2}' | sed -E 's/^([0-9]+)m([0-9\.]+)s$/\1 \2/g' | awk '{print $1*60+$2}' ; done | awk 'BEGIN{TOTAL=0;MAX=0;MIN=9999}{TOTAL+=$1;if(MAX<$1)MAX=$1;if(MIN>$1)MIN=$1}END{print "avg: "TOTAL/NR", max: "MAX", min: "MIN}'
avg: 0.0848, max: 0.103, min: 0.08

# sedrep氏
root@ubuntu16:~# for i in {1..10} ; do ( time cat big_alice.txt | sed -n '/play/p' | wc -l) 2>&1 | grep real | awk '{print $2}' | sed -E 's/^([0-9]+)m([0-9\.]+)s$/\1 \2/g' | awk '{print $1*60+$2}' ; done | awk 'BEGIN{TOTAL
=0;MAX=0;MIN=9999}{TOTAL+=$1;if(MAX<$1)MAX=$1;if(MIN>$1)MIN=$1}END{print "avg: "TOTAL/NR", max: "MAX", min: "MIN}'
avg: 1.0689, max: 1.086, min: 1.06

# awkrep氏
root@ubuntu16:~# for i in {1..10} ; do ( time cat big_alice.txt | awk '/play/{print}' | wc -l) 2>&1 | grep real | awk '{print $2}' | sed -E 's/^([0-9]+)m([0-9\.]+)s$/\1 \2/g' | awk '{print $1*60+$2}' ; done | awk 'BEGIN{TO
TAL=0;MAX=0;MIN=9999}{TOTAL+=$1;if(MAX<$1)MAX=$1;if(MIN>$1)MIN=$1}END{print "avg: "TOTAL/NR", max: "MAX", min: "MIN}'
avg: 0.8092, max: 0.817, min: 0.803

どうやら2番手はawkrep氏の模様。
では最後に1GBの huge_alice.txt を食わせてみましょう。やってることを同じなのでまとめて結果だけ。
あとついでに単純文字列のgrepならそのままより速いという fgrep も試してみます。

rank type file avg max min avg ratio
1 grep big_alice.txt 0.0848 0.103 0.08 x1.00
2 fgrep big_alice.txt 0.0948 0.104 0.01 x1.11
4 sed big_alice.txt 1.0689 1.086 1.06 x12.60
3 awk big_alice.txt 0.9092 0.817 0.803 x10.72
1 grep huge_alice.txt 14.5974 16.098 13.735 x1.00
2 fgrep huge_alice.txt 15.5718 16.519 14.608 x1.06
4 sed huge_alice.txt 16.414 17.42 15.559 x1.12
3 awk huge_alice.txt 16.1243 17.372 15.33 x1.10

結果は grep > fgrep > awk > sed の順となりました。
しかし big_alice.txt のときは他より10倍以上速かったgrepですが、 huge_alice.txt になると大差ないようになっています。
このあたりにはどんな理由があるんでしょうか。テストのやり方を含め、何か秘密がありそうです。
あと、fgrep君、君にはがっかりだよ。これも何か秘密が...。

おわりに

grepの全オプションを網羅しようと思ったんですがある程度ストーリー仕立てにすると思った以上に大変なのでやめました。

grepの奥は深い。