Bash 入門

Linux grep コマンド

grep("global regular expression print" の略)は、Bash スクリプトにおいて最も基本的かつ強力なコマンドラインツールの1つであり、プレーンテキストデータ内で正規表現(Regular Expression)にマッチする行を検索するために特化しています。極めて効率的なパターンスキャナー(Pattern Scanner)として機能し、ファイルやデータストリーム内の特定情報を素早く特定(Locate)することができます。

システムログ、設定ファイル、コードベース、その他のテキストベースの情報を処理する場合でも、grep を理解することは極めて重要です。なぜなら、UNIX 系環境において効率的なテキスト処理と分析を行うための基盤だからです。複雑なパターンに基づいて関連する行をフィルタリングおよび抽出(Extract)できるため、システム管理者、デベロッパー、データアナリストにとって不可欠なツールとなっています。

本章では、grep のコア機能、一般的なオプション、およびパターンマッチング機能を活用してさまざまなタスクを完了する方法について深く掘り下げます。

1. grep を理解する:基本構文と機能

grep のコアな役割は、入力ファイル内で指定されたパターン(Pattern)を含む行を検索することです。マッチする項目が見つかると、その行全体を標準出力(Standard Output、通常はターミナル)にプリントします。ファイルが指定されていない場合、またはファイル名の代わりにハイフン (-) が使用されている場合、grep は標準入力(Standard Input)からデータを読み取ります。これにより、パイプ(Pipe)を使用して他のコマンドと組み合わせる際に、非常に柔軟な運用が可能になります。

grep の基本構文は以下の通りです:

grep [オプション] パターン [ファイル...]
  • パターン (Pattern): grep が検索する正規表現、またはプレーンテキスト文字列(リテラル/Literal)です。
  • ファイル... (File...): grep が検索を実行する1つまたは複数のファイルです。省略した場合、grep は標準入力から読み取ります。
  • オプション (Options): 大文字と小文字を区別しない検索、リバースマッチング(反転マッチ)、行番号の表示など、grep の挙動を変更するために使用されます。

最もシンプルなユースケースである、プレーンテキスト文字列の検索から始めましょう。

1.1 リテラル文字列の検索

パターンがシンプルな文字シーケンスである場合、grep は指定されたファイルの各行からそのシーケンスの完全なマッチング(Exact Match)を探します。

サンプル1:単一ファイルの検索

以下のような内容の access.log というファイルがあると仮定します:

192.168.1.1 GET /index.html 200
10.0.0.5 POST /api/data 404
192.168.1.1 GET /images/logo.png 200
172.16.0.10 GET /admin 500
10.0.0.5 GET /about.html 200

文字列 "192.168.1.1" を含むすべての行を検索するには:

grep "192.168.1.1" access.log

出力:

192.168.1.1 GET /index.html 200
192.168.1.1 GET /images/logo.png 200

サンプル2:複数ファイルの検索

file1.txtfile2.txt、および file3.txt があると仮定します。

file1.txt:

apple banana cherry
date elderberry fig

file2.txt:

grape watermelon kiwi
lemon mango nectarine

file3.txt:

orange pear quince
raspberry strawberry tangerine

これらのファイルから "banana" が出現するすべての箇所を検索するには:

grep "banana" file1.txt file2.txt file3.txt

出力:

file1.txt:apple banana cherry

複数のファイルを検索する場合、grep は各マッチング行の先頭に、それが属するファイル名を追加します。

1.2 標準入力からの検索(パイプとの組み合わせ)

モジュール1で学んだように、パイプ (|) はあるコマンドの出力を別のコマンドの入力として渡すことを可能にします。grep は頻繁にこの方法で入力を受け取り、コマンドチェーンにおける強力なフィルター(Filter)として機能します。

サンプル:プロセスリストのフィルタリング

システム上で "apache" に関連する現在実行中のすべてのプロセス(Process)を検索するには:

ps aux | grep "apache"

出力(サンプル):

root 1234 0.0 0.1 123456 6789 ? Ss Oct01 0:00 /usr/sbin/apache2 -k start
www-data 5678 0.0 0.0 123456 3456 ? S Oct01 0:00 /usr/sbin/apache2 -k start
www-data 5679 0.0 0.0 123456 3456 ? S Oct01 0:00 /usr/sbin/apache2 -k start
user 9876 0.0 0.0 123456 7890 pts/0 S+ 10:30 0:00 grep apache

最後の行である grep apache に注目してください。これが発生するのは、grep 自身も実行中であり "apache" を検索しているプロセスであるため、自身のプロセスを見つけてしまうためです。これを除外するには、パターンを工夫するか、grep -v を使用します。grep -v については後述します。

2. 正規表現と組み合わせた grep の使用

grep の真の威力は、検索パターンとして正規表現 (REs: Regular Expressions) を使用できる点にあります。前章で説明したように、正規表現は検索パターンを定義する文字シーケンスであり、単純なリテラル文字列よりもはるかに柔軟で強力なマッチングを可能にします。grep はデフォルトで主に基本正規表現 (BRE: Basic Regular Expressions) を使用しますが、-E オプションを通じて拡張正規表現 (ERE: Extended Regular Expressions) に切り替えることができます。ERE はより多くの機能を提供し、特殊文字のエスケープ(Escape)を必要としません。

重要な正規表現の概念と、grep がそれらをどのように使用するかを復習しましょう。

2.1 基本正規表現 (BRE)

デフォルトでは、grep はパターンを BRE として解析(Parse)します。つまり、一部のメタ文字(Meta-characters)は、バックスラッシュ (\) でエスケープして特殊文字として扱わせる必要があり、そうしないと通常のリテラル文字として扱われます。

^ (キャレット): 行の先頭アンカー (Anchor)
行の先頭にマッチします。

サンプル: access.log 内で "192.168" で始まる行を検索:

# 修正:ドット . は正規表現では特殊文字です。リテラルのドットにマッチさせるには、エスケープする必要があります。
# "192.168" で始まる行にマッチング
grep "^192\.168" access.log

出力:

192.168.1.1 GET /index.html 200
192.168.1.1 GET /images/logo.png 200

$ (ドル記号): 行の末尾アンカー
行の末尾にマッチします。

サンプル: access.log 内で "200" で終わる行を検索:

grep "200$" access.log

出力:

192.168.1.1 GET /index.html 200
192.168.1.1 GET /images/logo.png 200
10.0.0.5 GET /about.html 200

. (ドット): 任意の単一文字

(改行を除く)任意の単一文字にマッチします。

サンプル: "GET" を含み、その後に任意の1文字が続き、さらに "index.html" が続く行を検索:

grep "GET.index\.html" access.log

これは "GET /index.html" にマッチします(ここでのスペースが . にマッチした文字です)。

出力:

192.168.1.1 GET /index.html 200

* (アスタリスク): 0回以上の出現

直前の文字またはグループ(Group)の0回以上の出現にマッチします。

サンプル: "GET" を含み、その後に任意の文字が任意の数続き、さらに "html" が続く行を検索:

grep "GET.*html" access.log

出力:

192.168.1.1 GET /index.html 200
10.0.0.5 GET /about.html 200

[] (角括弧): 文字セット (Character Set)

角括弧内の任意の単一文字にマッチします。

サンプル: ステータスコードの最後の数字が "2"、"4"、または "5" である行を検索:

grep "[245]00$" access.log

出力:

192.168.1.1 GET /index.html 200
10.0.0.5 POST /api/data 404
192.168.1.1 GET /images/logo.png 200
172.16.0.10 GET /admin 500
10.0.0.5 GET /about.html 200

[^] (角括弧内のキャレット): 反転文字セット

角括弧内にない任意の単一文字にマッチします。

サンプル: IPアドレスの第2オクテットの数字が 0 または 1 でない行を検索:

grep "[0-9]\.[^01]\." access.log

説明: このパターンはデモンストレーション用に簡略化されています。堅牢なIPマッチングルールははるかに複雑になります。

出力(想定されるログ内容に依存):

172.16.0.10 GET /admin 500

(もし 192.2.3.4 があれば、第2セグメントの 2 にマッチします。)

\{n\} (中括弧): クオンティファイア (Quantifier / 数量子)

直前の文字が正確に n 回出現することにマッチします。BRE ではエスケープが必要です。

サンプル: 'o' が連続して正確に3回出現する行(例:"fooo")を検索:

echo "foo" | grep "o\{2\}"   # マッチング
echo "fooo" | grep "o\{3\}"  # マッチング
echo "foooo" | grep "o\{3\}" # 'o' の次に 'o'、さらに 'o' が続くためマッチング

2.2 grep -E を使用した拡張正規表現 (ERE) の有効化

より複雑なパターン、特に +?|、および () を含むパターンの場合、通常は拡張正規表現 (ERE) を使用する方が簡単です。grep に -E オプションを追加する(または同等の egrep コマンドを使用する)ことで、ERE を有効にすることができます。ERE では、+?|()、および {} などのメタ文字をエスケープする必要はありません。

+ (プラス記号): 1回以上の出現

直前の文字またはグループの1回以上の出現にマッチします。

サンプル: "Error" の後に1つ以上の数字が続く行を検索:

# ログエントリが "Error500: Internal Server Error" のような形式であると仮定
grep -E "Error[0-9]+" error.log

? (疑問符): 0回または1回の出現

直前の文字またはグループの0回または1回の出現にマッチします(文字をオプショナルにします)。

サンプル: "color" または "colour" を含む行を検索:

grep -E "colou?r" text.txt

| (パイプ): OR (論理和) 演算子

パイプの前または後ろのパターンにマッチします。

サンプル: access.log 内で "ERROR" または "WARNING" を含む行を検索:

grep -E "ERROR|WARNING" access.log

出力(該当行が存在すると仮定):

172.16.0.10 GET /admin 500 ERROR: Server Fault
10.0.0.5 POST /api/data 404 WARNING: Resource not found

() (丸括弧): グルーピング (Grouping)

複数の文字または式をグループ化し、グループ全体にクオンティファイアや OR 演算子を適用できるようにします。

サンプル: 大文字または小文字の "apple" または "banana" を含む行を検索:

grep -E "(Apple|Banana)" fruit.txt       # 大文字小文字を区別するマッチングのみ有効
grep -E -i "(Apple|Banana)" fruit.txt    # Apple, apple, Banana, banana 等にマッチング

() がない場合:grep -E "Apple|Banana" の交替(Alternation)は同様に機能します。複数の文字にクオンティファイアを適用する場合、グルーピングはより強力になります。例えば (ab)+ は "ab"、"abab" などにマッチします。

ワード境界 (Word Boundary) とキャラクタークラス (Character Class)

(通常は -E または特定のオプションと組み合わせて使用)

\b (ワード境界) / -w オプション:

\b は単語の先頭または末尾の空文字列にマッチします。BRE を使用する場合、通常はエスケープする必要があります:grep '\bword\b'-E を使用する場合、\b は通常そのまま機能します。ただし、単語全体のマッチングには、直接 grep -w オプションを使用する方が簡単な場合が多いです。

サンプル: 完全に一致する単語 "admin" のみを検索し、"administrator" や "administer" は検索しない:

grep -w "admin" access.log

出力:

172.16.0.10 GET /admin 500

キャラクタークラス(通常、Perl 互換の正規表現を有効にするには -E または -P が必要):

  • \d : 任意の数字 (0-9) にマッチ。(一部の実装では grep -P または grep -E が必要)。
  • \w : 任意の単語文字(英数字 + アンダースコア)にマッチ。(grep -P が必要)。
  • \s : 任意の空白文字(スペース、タブ、改行など)にマッチ。(grep -P が必要)。
  • \S : 任意の非空白文字にマッチ。(grep -P が必要)。

POSIX キャラクタークラスを使用すると、移植性が向上します(grep でネイティブにサポートされています):

  • [[:digit:]] : 数字([0-9] と同等)
  • [[:alpha:]] : アルファベット文字
  • [[:alnum:]] : 英数字
  • [[:space:]] : 空白文字
  • [[:upper:]] : 大文字
  • [[:lower:]] : 小文字

サンプル: "GET" を含み、その後に1つ以上のスペース、続いてパスの任意の英数字文字、そして ".html" で終わる行を検索:

grep -E "GET\s+[[:alnum:]\/]+\.html" access.log

\s+ には ERE が必要です。[[:alnum:]\/]+ は1つ以上の英数字またはフォワードスラッシュにマッチします。

出力:

192.168.1.1 GET /index.html 200
10.0.0.5 GET /about.html 200

3. grep のコアな高度オプション

grep は、その動作を制御し、検索を最適化し、出力フォーマットを変更するための豊富なオプションセットを提供します。

-i, --ignore-case : パターンと入力データにおける大文字・小文字の区別を無視(Ignore)します。

サンプル: 大文字・小文字を問わず "error" を検索。

grep -i "error" access.log

これは "error"、"Error"、"ERROR" などにマッチします。

-v, --invert-match : リバースマッチング;パターンにマッチしない行を選択します。

サンプル: access.log 内でステータスコードが "200" でないすべての行を表示。

grep -v "200$" access.log

出力:

10.0.0.5 POST /api/data 404
172.16.0.10 GET /admin 500

先ほどの ps aux | grep apache を最適化:

ps aux | grep "apache" | grep -v "grep"

出力(grep プロセス自体を除外):

root 1234 0.0 0.1 123456 6789 ? Ss Oct01 0:00 /usr/sbin/apache2 -k start
www-data 5678 0.0 0.0 123456 3456 ? S Oct01 0:00 /usr/sbin/apache2 -k start
www-data 5679 0.0 0.0 123456 3456 ? S Oct01 0:00 /usr/sbin/apache2 -k start

-c, --count : 通常の出力を抑制し、各入力ファイルにおけるマッチング行のカウント数(Count)を出力します。

サンプル: access.log 内に "192.168.1.1" を含む行がいくつあるかをカウント。

grep -c "192.168.1.1" access.log

出力:

2

-n, --line-number : 各行の出力の前に、入力ファイル内の行番号(1から開始)を追加します。

サンプル: access.log 内で "GET" を検索し、行番号を表示。

grep -n "GET" access.log

出力:

1:192.168.1.1 GET /index.html 200
3:192.168.1.1 GET /images/logo.png 200
4:172.16.0.10 GET /admin 500
5:10.0.0.5 GET /about.html 200

-l, --files-with-matches : 通常の出力を抑制し、マッチング項目を含むファイル名のみを出力します。

サンプル: カレントディレクトリ内のどのファイルが "error" を含んでいるか?

grep -l "error" *.log

出力(error.log が "error" を含んでいると仮定):

error.log

-r, --recursive / -R, --dereference-recursive : ディレクトリを再帰的に検索します。-R はシンボリックリンク(Symbolic Link)を辿りますが、-r は辿りません。

サンプル: カレントディレクトリおよびそのサブディレクトリ内のすべての Python ファイルから "TODO" コメントを検索。

grep -r "TODO" *.py

修正: *.py はカレントディレクトリのみに適用されます。すべての .py ファイルを再帰的に検索するには、find とパイプを使用するのがベストです:

find . -name "*.py" | xargs grep "TODO"

しかし、grep -r はディレクトリをトラバース(Traverse)するために設計されています。目的がすべてのファイルを検索することであれば、grep -r "TODO" . で問題ありません。.py ファイルのみを検索する場合は、grep -r --include='*.py' "TODO" . がより具体的です。よりシンプルな再帰的ケースを見てみましょう。

サンプル: カレントディレクトリおよびそのサブディレクトリ内のすべてのファイルから文字列 "password" を検索。

grep -r "password" .

コンテキストコントロールオプション (Context Control Options):

  • -A N, --after-context=N : マッチング行の後の N 行の末尾コンテキスト (After) をプリントします。
  • -B N, --before-context=N : マッチング行の前の N 行の先行コンテキスト (Before) をプリントします。
  • -C N, --context=N : マッチング行の周囲の前後 N 行のコンテキスト (Context) をプリントします。

サンプル: access.log で "ERROR" を見つけた場合、エラー行とその前2行および後2行を表示。

grep -C 2 "ERROR" access.log

-o, --only-matching : マッチング行の中でマッチした部分のみを(空でなく)プリントし、各マッチ部分を別々の行に出力します。

サンプル: access.log からすべてのIPアドレスを抽出(Extract)する(簡易パターン)。

grep -E -o "([0-9]{1,3}\.){3}[0-9]{1,3}" access.log

出力:

192.168.1.1
10.0.0.5
192.168.1.1
172.16.0.10
10.0.0.5

注:これは簡易的なIPアドレスの正規表現です。完全に堅牢な正規表現はさらに複雑になりますが、これは -o の動作を効果的に示しています。

-x, --line-regexp : 行全体に完全にマッチする結果のみを選択します。

サンプル: "OK" だけを含む行を検索。

echo "OK" | grep -x "OK"       # マッチング成功
echo "NOT OK" | grep -x "OK"   # マッチング失敗

4. 実践ケース:grep を使用したログファイルの解析

本モジュールの実践ケースでは、ログファイルを解析して潜在的な問題を特定します。grep はこの点で非常に有用であり、関連するログエントリを素早くフィルタリングするための第一の防衛線として機能します。

より現実的(とはいえ簡略化されていますが)な system.log ファイルを使用してみましょう:

Oct  1 08:30:01 servername sshd[12345]: Accepted password for user1 from 192.168.1.10 port 54321 ssh2
Oct  1 08:30:05 servername kernel: Out of memory: Kill process 6789 (mysqld)
Oct  1 08:30:10 servername CRON[23456]: (root) CMD (command -v lsb_release >/dev/null && lsb_release -cs)
Oct  1 08:30:15 servername sshd[12346]: Failed password for user2 from 10.0.0.20 port 12345 ssh2
Oct  1 08:30:20 servername kernel: CPU load high, throttling processes.
Oct  1 08:30:25 servername systemd[1]: Started User Manager for UID 1000.
Oct  1 08:30:30 servername sshd[12347]: Failed password for invalid_user from 10.0.0.21 port 12346 ssh2
Oct  1 08:30:35 servername webserver[7890]: [error] Client 172.16.0.30: Request denied for resource /admin
Oct  1 08:30:40 servername kernel: INFO: task kworker/u12:0:22 blocked for more than 120 seconds.
Oct  1 08:30:45 servername webserver[7891]: [warning] Large file download detected from 172.16.0.31

シナリオ1:すべてのエラーメッセージの識別

"error" または "ERROR" を含むすべての行を見つけたいとします。

grep -i "error" system.log

出力:

Oct  1 08:30:05 servername kernel: Out of memory: Kill process 6789 (mysqld)
Oct  1 08:30:35 servername webserver[7890]: [error] Client 172.16.0.30: Request denied for resource /admin

ここでは同時に「Out of memory」も検出しました(「memory」という単語は、注意しないと特定の複雑なマッチングにおいて曖昧さを引き起こしやすいためですが、ここでは単に error を検索したためです)さらに明示的な [error] メッセージも検出しています。より正確にするには、ワード境界を使用するか、明確に [error] を検索することになるでしょう。

それでは、[error] または ERROR: メッセージを正確に検索してみましょう:

grep -E -i "\[error\]|ERROR:" system.log
  • \[error\] は、バックスラッシュを使用して角括弧をエスケープし、それらを正規表現のメタ文字ではなくリテラル文字として扱います。
  • ERROR: はリテラル文字列です。
  • -E は、| (OR) 演算子を使用するために拡張正規表現を有効にします。
  • -i は検索で大文字・小文字を区別しません。

出力:

Oct  1 08:30:35 servername webserver[7890]: [error] Client 172.16.0.30: Request denied for resource /admin

これにより、マッチングがはるかに正確になりました。

シナリオ2:失敗したログイン試行の検索

"Failed password" に言及している sshd エントリに関心があります。

grep "Failed password" system.log

出力:

Oct  1 08:30:15 servername sshd[12346]: Failed password for user2 from 10.0.0.20 port 12345 ssh2
Oct  1 08:30:30 servername sshd[12347]: Failed password for invalid_user from 10.0.0.21 port 12346 ssh2

シナリオ3:特定のIPアドレスのモニタリング

特定のIPアドレス(例:"10.0.0.20")に関連するすべてのログエントリを検索します。

grep "10\.0\.0\.20" system.log

ドットは正規表現のメタ文字であるため、エスケープ(Escape)する必要があります。

出力:

Oct  1 08:30:15 servername sshd[12346]: Failed password for user2 from 10.0.0.20 port 12345 ssh2

シナリオ4:システムの問題を示すカーネルメッセージの識別

(例:"Out of memory" または "CPU load high")

grep -E "kernel: (Out of memory|CPU load high)" system.log

ここで、() はカーネルメッセージに特有の OR 条件をグループ化(Group)しています。

出力:

Oct  1 08:30:05 servername kernel: Out of memory: Kill process 6789 (mysqld)
Oct  1 08:30:20 servername kernel: CPU load high, throttling processes.

これらの例は、強力かつ柔軟な初期のログ分析ツールとしての grep の役割を示しており、システム管理者が大量のテキストデータの中から重要なイベントや潜在的な問題を迅速に特定(Locate)することを可能にします。