Linuxのファイル内を正規表現で検索する基本コマンドはgrepです。grep PATTERN FILEは、パターンに一致する行を表示します。便利な一方、正規表現の記号、シェルの引用、検索範囲、文字コード、終了状態を理解しないと、「見つからない」「多すぎる」「秘密情報を画面へ出した」という問題が起きます。まず既知の小さなテストファイルでパターンを確認し、本番では対象ディレクトリとファイル形式を限定してください。
最小の検索例
文字列ERRORを含む行を探す例です。パターンとファイル名を引用し、--でオプションの終わりを示します。grepは既定で一致した行全体を標準出力へ出します。ログ行にトークン、メールアドレス、URLパラメーターなどが含まれる場合、結果を公開チケットや共有画面へそのまま出さないでください。
grep -- 'ERROR' './application.log'
grepは通常、大文字と小文字を区別します。-iで無視できますが、ロケールとUnicodeの大文字小文字対応が関係し、識別子や秘密値の検索では誤一致を増やすことがあります。要件が「ERRORまたはError」なのか「英字の大小をすべて無視」なのかを先に決めます。
正規表現ではなく固定文字列なら-F
検索語に.、*、角括弧などが含まれ、文字どおり探したい場合はgrep -Fを使います。たとえばIPアドレス風の192.0.2.10を通常の正規表現で書くと、点は任意の1文字として扱われます。バックスラッシュでエスケープできますが、固定文字列検索の方が意図を示しやすく、ユーザー入力を検索するときにも安全です。
grep -F -- '192.0.2.10' './access.log'
-Fは正規表現を無効にしますが、シェルの引用は依然必要です。検索語に改行やNULが含まれる、ファイルがバイナリ、文字コードが異なる場合は別の設計が必要です。検索語をコマンド文字列へ連結してevalしません。配列や引数として一つの値のまま渡します。
BREとEREを区別する
GNU grepの既定は基本正規表現(BRE)です。-Eを付けると拡張正規表現(ERE)になり、+、?、|、丸括弧などをより直感的な記法で使えます。GNU grepではBREとEREは主に表記法の違いですが、他実装の細部や拡張は異なります。記事で見たパターンをコピーする前に、その例がBRE、ERE、PCREのどれかを確認します。
grep -E -- 'ERROR|WARN' './application.log'
grep -E -- 'status=[45][0-9]{2}' './access.log'
PCREを使う-PはGNU grepでもビルドや版により利用可能性や機能が異なります。lookaroundなどが必要だからと安易に依存せず、移植性が必要ならEREやawkで表現できないか検討します。正規表現が複雑になるほどレビューとテストが重要です。
代表的な正規表現記号
^:行頭。$:行末。CRLFファイルでは行末の復帰文字に注意する。.:任意の1文字。文字どおりの点ならEREでは\.などとエスケープする。*:直前の要素を0回以上。EREの+は1回以上、?は0回または1回。[abc]:a、b、cのいずれか。[^abc]はそれ以外。{m,n}:直前要素の回数範囲。EREではそのまま、BREでは記法差に注意する。(...)と|:EREでグループ化と選択肢を表す。
シェルから守るためパターンを引用する
正規表現には*、?、[ ]、$などシェルにも意味がある文字が含まれます。シングルクォートで囲めば、Bashによるファイル名展開や変数展開を防ぎ、grepへパターンをそのまま渡せます。ダブルクォートは変数展開を行うため、固定パターンにはシングルクォートが分かりやすい選択です。
grep -E -- '^[[:space:]]*server_name[[:space:]]+' './example.conf'
パターン自体が-で始まる場合、-eでパターンだと明示するか--の後へ置きます。ファイル名も-で始まり得るため、./filenameの形にする方法があります。外部入力を正規表現として許すと、非常に遅いパターンや想定外の情報抽出につながることがあります。固定文字列で足りるなら-Fを使います。
行番号とファイル名を付ける
-nは行番号、-Hはファイル名を出します。複数ファイルではファイル名が通常自動表示されますが、スクリプトの安定した出力形式が必要なら明示します。-oは一致部分だけを出し、同じ行に複数一致があれば複数行になります。結果行数を元ファイルの該当行数と誤解しないでください。
grep -nH -E -- 'ERROR|WARN' ./*.log
前後の文脈を表示する
-Aは一致後、-Bは一致前、-Cは前後の行を追加します。スタックトレースや設定の周辺確認に便利ですが、表示範囲が増えるほど秘密情報を含む可能性も高まります。まず件数だけを確認し、必要なファイルと時刻へ絞ってから前後行を表示します。
grep -n -C 3 -- 'connection timeout' './application.log'
一致しない行、件数、ファイル名だけ
-v:一致しない行を選ぶ。条件を誤るとほぼ全内容を表示するため注意する。-c:一致した行数をファイルごとに出す。同じ行の複数一致は通常1行として数える。-l:一致を含むファイル名だけ出す。内容を画面へ出したくない一次調査に向く。-L:一致を含まないファイル名を出す。検索対象の範囲が正しいか確認する。-m NUM:各ファイルの一致数に上限を設け、大量出力を抑える。-q:通常の出力を抑え、終了状態で存在確認する。エラーの扱いをマニュアルで確認する。
終了状態0・1・2を扱う
GNU grepは、一致があれば0、一致がなければ1、エラーなら通常2を返します。したがって、終了状態1を一般的な「コマンド故障」と同じに扱うと、存在確認で誤警報になります。逆に|| trueですべてを成功へ変えると、ファイルを開けないエラーまで隠します。分岐で0、1、それ以外を分けます。
if grep -qF -- 'ready' './status.txt'; then
printf '%s\n' 'found'
else
status=$?
if [[ $status -eq 1 ]]; then
printf '%s\n' 'not found'
else
printf 'grep failed: %s\n' "$status" >&2
exit "$status"
fi
fi
再帰検索は範囲を限定する
grep -rはディレクトリを再帰検索します。ルートディレクトリ全体を対象にすると、仮想ファイルシステム、巨大な依存フォルダー、バックアップ、秘密鍵、他利用者データへ入り、負荷と情報露出を招きます。サービスの設定ディレクトリなど、所有者が明確な小さな範囲を指定し、--includeや--exclude-dirで形式を限定します。シンボリックリンクを追う-Rは検索範囲が想定外へ広がるため違いを確認します。
grep -r -nH -E --include='*.conf' --exclude-dir='.git' -- 'timeout[[:space:]]*=' './config/'
再帰検索中に権限エラーが出ても、エラーを全面的に捨てないでください。対象外ディレクトリを明示的に除外する、アクセス権を持つ担当者へ依頼するなど、理由を残します。管理者権限で全体を読めるようにすると、必要以上の秘密情報へアクセスすることになります。
バイナリ、文字コード、ロケール
grepはバイナリらしい入力を検出すると、一致行ではなく「binary file matches」のような通知を出すことがあります。-aでテキスト扱いを強制できますが、端末制御文字を表示する危険があるため、ファイル形式を確認せず使いません。UTF-16や圧縮形式は専用の変換・展開経路を新しい作業コピーに対して使います。ロケールは[a-z]の範囲や[[:alpha:]]などの文字クラス、大文字小文字の照合へ影響します。再現可能な自動処理ではロケール要件を明示します。
正規表現をテストする
- 一致すべき最小例、一致してはいけない例、空行、長い行、境界値を含む小さなテストファイルを作る。
- BRE、ERE、固定文字列のどれを使うか決め、コマンド行へ明示する。
- パターンを引用し、テストファイルだけへ実行して一致行と終了状態を確認する。
- 本番ではまず-lや-cで候補と件数を確認し、必要な範囲だけ内容を表示する。
- 処理時間と出力量を監視し、巨大行、再帰範囲、ネットワークストレージを確認する。
- 結果を共有する前に秘密情報を確認し、必要なら承認済みの方法でマスキングする。
固定の文字を探すならgrep -F、選択肢や回数を使うならgrep -Eから始めると意図が明確です。引用、検索範囲、終了状態、ロケールをセットで確認し、再帰検索や複雑な正規表現は小さなテストで検証してから使ってください。

コメント