grep で行を検索する
grep はファイルや標準入力から、パターンに一致する行を取り出すコマンドです。grep "パターン" ファイル の形で使い、よく使うオプションに大文字小文字を無視する -i、一致しない行だけ出す -v、一致した件数を数える -c、行番号を付ける -n があります。-r を付ければディレクトリ以下を再帰的に検索できます。パイプと組み合わせ、別コマンドの出力から必要な行だけを絞り込む使い方も定番です。
正規表現を実際に使う最初のコマンドが grep です。grep は「ファイルや標準入力の中から、パターンに一致する行を抜き出して表示する」コマンドで、名前は global regular expression print の頭文字に由来します。基本の形は grep 'パターン' ファイル で、指定したファイルの各行を上から順に調べ、パターンに一致した行だけを画面に出します。パターンには前の項目で学んだ正規表現がそのまま使え、ここでは grep や sed が既定とする基本正規表現(BRE)の方言で解釈されます。「探す」という最も基本的な操作を担うため、テキスト処理の入り口として絶え間なく使うコマンドです。
まず使う基本形とクォート
たとえば grep '^root' /etc/passwd と打つと、/etc/passwd の中から root で始まる行だけが表示され、root:x:0:0:root:/root:/bin/bash のような行が返ります。bash$ /etc/passwd のように $ を使えば bash で終わる行を、^r..t なら r と t のあいだが任意の2文字の行を選べます。ここでもパターンはシングルクォートで囲むのが鉄則です。^ $ * といった記号をシェルに先取りされないためで、囲み忘れると意図しない展開やエラーの原因になります。ファイル名を省略してパイプ | からの入力に対して使うこともでき、その場合は別コマンドの出力を絞り込むフィルタとして働きます。
よく使うオプション
grep は組み合わせるオプションで使い勝手が大きく変わります。-i は大文字小文字を区別せず探します(ignore case)。ログには ERROR と error が混在しがちなので、grep -i 'error' app.log とすればどちらも拾えます。-v は条件を反転し、一致しない行だけを残します(invert)。grep -v '^#' file はコメント行を除いて実効行だけを見る定番で、空行も消したいなら grep -v '^#' file | grep -v '^$' のようにつなげます。-c は一致した行数を数え(count)、-n は各行の先頭に行番号を付けます(number)。ここで注意したいのは、-c が数えるのは「一致した行の数」であって「出現回数」ではない点です。1行に同じ語が3回出ても、-c はその行を1と数えます。
再帰検索と部分一致の制御
-r を付けると、指定したディレクトリ以下を再帰的にたどって検索します(recursive)。grep -r 'PermitRootLogin' /etc/ssh/ のように使い、見つかった「ファイル名:一致行」が並びます。さらに -l(小文字のエル)を足すと中身は出さず、該当したファイル名だけを一覧でき(list)、grep -rl 'nameserver' /etc/ のように「どの設定ファイルにこの語があるか」を探すときの王道です。これらのオプションは grep -rin 'timeout' . のようにまとめて組み合わせて使え、現在地以下を大文字小文字を無視しつつ行番号付きで再帰検索する、といった一行が日常的に役立ちます。部分一致の暴発を防ぐには -w が役立ちます。grep 'cat' words.txt は category や locate の中の cat まで拾いますが、grep -w 'cat' は前後が区切りのときだけ一致させ、独立した単語 cat だけを残します。語そのものを探したいのに似た語まで混ざってしまうときは、-w を思い出してください。
一致部分だけを取り出す -o
-o は、行全体ではなくパターンに当たった断片だけを1行ずつ出力します(only matching)。ログからIPアドレスらしき並びだけを抜き出す例が分かりやすく、grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' access.log と書けば、192.168.1.10 のような一致部分だけが縦に並びます。ここで -E を併用しているのは、+ という量指定子をエスケープなしで使うためです(次の項目で詳しく扱います)。なお、この式は「形がIPに似た文字列」を拾うだけで、999.999.999.999 のような実在しない値も一致してしまうため、厳密な値の検証は別途必要だ、という限界も覚えておくとよいでしょう。-o は、行ではなく「中の断片」を集めたいときの専用スイッチです。
よくある失敗と実務の使いどころ
失敗として多いのは、パターンをクォートで囲み忘れて $ や * がシェルに解釈されてしまうこと、そして -c を「出現回数」と誤解することです。また BRE のまま grep 'a+' と書いて + が量指定子として効かず一致しない、というのも頻出で、これは次の項目の -E で解決します。検索結果が0件のとき、grep は終了ステータスとして非ゼロを返すので、スクリプトの中で if grep -q 'pattern' file; then … のように「一致したかどうか」だけを条件分岐に使うこともできます。-q は何も表示せず結果だけを返すオプションで、存在チェックに重宝します。実務では、grep の真価はパイプと組み合わせたときに発揮されます。ps aux | grep 'nginx' で動いているプロセスを絞ったり、設定の塊から grep で必要な行だけを取り出してから sed や awk にさらに渡したり、grep 'error' app.log | wc -l で件数を数えたりと、調査と集計の起点になります。前後の文脈もあわせて見たいときは -A(後ろ)-B(前)-C(前後)で一致行の周辺も表示でき、grep -C2 'error' app.log のようにエラーの前後2行まで確認できます。「まず grep で行を絞り、必要ならパイプで次の道具へ渡す」——この流れが、正規表現を使った作業の基本リズムです。