grepで下の階層のすべてのファイルを再帰的に検索する完全ガイド【-rオプション徹底解説】

2011年7月26日

ディレクトリ内のすべてのファイルから特定の文字列を一度に探したい。そんなときに欠かせないのが grep の再帰検索オプションです。

本記事では grep -r の基本から、ファイル種別での絞り込み、正規表現の活用、そして高速代替ツール ripgrep との比較まで、実務で即使えるコマンド例を網羅します。

grepコマンドでターミナルからファイルを再帰検索しているシーン

目次

  1. 基本:-r オプションで再帰検索
  2. -R との違い(シンボリックリンクの扱い)
  3. 特定のファイルだけを対象にする –include
  4. 対象から除外する –exclude / –exclude-dir
  5. よく使う grep オプション一覧
  6. 正規表現を活用した高度な検索
  7. 検索結果を活用する実践パターン
  8. ripgrep(rg)との比較
  9. まとめ

基本:-r オプションで再帰検索

カレントディレクトリ以下のすべてのファイルに対して再帰的に検索するには、-r(または --recursive)を使います。

grep -r "検索したい文字列" .

* を使った書き方も一部の環境で動作しますが、-r を使う方が確実です。

# NG: シェルのグロブ展開に依存するため、深い階層は探せない
grep "find_keyword" *

# OK: -r でディレクトリを再帰的に辿る
grep -r "find_keyword" .

特定のディレクトリを起点にする

カレントディレクトリ以外を起点にする場合は、パスを指定します。

# /var/log 以下を全検索
grep -r "ERROR" /var/log

# 複数ディレクトリを同時に指定
grep -r "TODO" ./src ./lib

-R との違い(シンボリックリンクの扱い)

-r-R はほぼ同じですが、シンボリックリンクの扱いが異なります。

オプション シンボリックリンク先のディレクトリ
-r 辿らない
-R 辿る
# シンボリックリンク先も含めて検索
grep -R "config" .

シンボリックリンクが複雑に絡み合う環境では -R で無限ループが起きる可能性があるため、通常は -r を推奨します。


特定のファイルだけを対象にする –include

ディレクトリ以下を全検索するとバイナリファイルや不要なファイルも引っかかります。--include でファイルパターンを絞り込みましょう。

# .py ファイルのみ検索
grep -r --include="*.py" "import os" .

# .js と .ts ファイルのみ検索
grep -r --include="*.js" --include="*.ts" "console.log" .

# README系ファイルのみ
grep -r --include="README*" "install" .

--include は複数指定が可能です。パターンはシェルのグロブ形式(*, ?, [...])で書きます。

--includeオプションを使ってPythonファイルだけを対象にgrepする端末画面


対象から除外する –exclude / –exclude-dir

node_modules.git など、検索したくないファイル・ディレクトリを除外します。

# node_modules ディレクトリを除外
grep -r --exclude-dir="node_modules" "require(" .

# .git と dist を除外
grep -r --exclude-dir=".git" --exclude-dir="dist" "TODO" .

# ログファイルを除外
grep -r --exclude="*.log" "ERROR" .

# まとめて除外(よくある組み合わせ)
grep -r \
  --exclude-dir=".git" \
  --exclude-dir="node_modules" \
  --exclude-dir="vendor" \
  --exclude="*.min.js" \
  "secret_key" .

よく使う grep オプション一覧

再帰検索と組み合わせると便利なオプションをまとめました。

オプション 短縮形 説明
--recursive -r ディレクトリを再帰的に検索
--ignore-case -i 大文字小文字を無視
--line-number -n 行番号を表示
--count -c マッチした行数だけ表示
--files-with-matches -l マッチしたファイル名のみ表示
--files-without-match -L マッチしなかったファイル名を表示
--invert-match -v マッチしない行を表示
--word-regexp -w 単語単位でマッチ
--extended-regexp -E 拡張正規表現を使用(ERE)
--perl-regexp -P Perl互換正規表現を使用(PCRE)
--context=N -C N マッチ行の前後N行を表示
--before-context=N -B N マッチ行の前N行を表示
--after-context=N -A N マッチ行の後N行を表示
--color マッチ箇所をカラー表示

実用的な組み合わせ例

# 大文字小文字無視 + 行番号表示
grep -rni "error" ./src

# マッチしたファイル名だけ表示(パイプラインで使いやすい)
grep -rl "TODO" . | head -20

# 前後3行のコンテキストを表示
grep -r -C 3 "raise Exception" .

# 単語単位で "log" を検索("logger"や"blog"は除外)
grep -rw "log" .

正規表現を活用した高度な検索

grep は基本的に BRE(基本正規表現)を使いますが、-E で ERE(拡張正規表現)、-P で PCRE(Perl互換正規表現)が使えます。

基本正規表現(BRE)

# "error" または "Error" を検索
grep -r "error\|Error" .

# 行頭が # のコメント行を検索
grep -r "^#" .

# 行末が semicolon で終わる行
grep -r ";$" .

拡張正規表現(-E)

# "error" または "Error" または "ERROR"
grep -rE "error|Error|ERROR" .

# 3桁以上の数字
grep -rE "[0-9]{3,}" .

# URLパターン
grep -rE "https?://[a-zA-Z0-9./?=_-]+" .

# 関数定義を探す(Python)
grep -rE "^def [a-z_]+\(" --include="*.py" .

Perl互換正規表現(-P)

より高度なパターンマッチが必要な場合は -P を使います。

# メールアドレスのパターン
grep -rP "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" .

# 先読み(lookahead)で "password" の後に数字が来る行
grep -rP "password(?=\d)" .

# IPアドレスのパターン
grep -rP "\b(?:\d{1,3}\.){3}\d{1,3}\b" /var/log

検索結果を活用する実践パターン

grepの検索結果をパイプでxargsやawkに渡して処理する概念図

マッチしたファイルを一括で別コマンドに渡す

# "TODO" が含まれるファイルを vim で開く
grep -rl "TODO" . | xargs vim

# grep のマッチ行をファイルに保存
grep -rn "FIXME" . > fixme_list.txt

# 大量のファイルを xargs で安全に処理(ファイル名のスペース対策)
grep -rl "deprecated" . | xargs -I {} echo "File: {}"

マッチ件数を集計する

# ファイルごとにマッチ件数を表示
grep -rc "import" --include="*.py" . | grep -v ":0"

# 合計件数を集計
grep -rc "import" --include="*.py" . | awk -F: '{sum += $2} END {print "Total:", sum}'

プロジェクト内の未使用コードを探す

# 定義されている関数名を収集してから、呼び出し元を検索
grep -rE "^def ([a-z_]+)\(" --include="*.py" . | \
  sed 's/.*def \([a-z_]*\)(.*/\1/' | \
  while read func; do
    count=$(grep -rn "$func" --include="*.py" . | wc -l)
    [ "$count" -le 1 ] && echo "未使用の可能性: $func"
  done

設定ファイルから値を抽出する

# .env ファイルからキーと値を一覧表示
grep -r --include=".env*" "^[A-Z_]=" . | grep -v ".git"

# YAML ファイルから特定キーの値を抽出
grep -rE "^\s+port:\s+[0-9]+" --include="*.yml" .

ripgrep(rg)との比較

近年、grep -r の代替として ripgrep(コマンド名:rg)が広く使われています。

ripgrep の特徴

  • .gitignore を自動的に読み込み、無視ファイルをスキップ
  • Rust 製で非常に高速(大規模プロジェクトで顕著)
  • デフォルトで バイナリファイルをスキップ
  • カラー表示・行番号表示がデフォルト有効

インストール

# macOS
brew install ripgrep

# Ubuntu / Debian
sudo apt install ripgrep

# Cargo(Rust)
cargo install ripgrep

grep -r との対応表

操作 grep ripgrep
再帰検索 grep -r "pattern" . rg "pattern"
大文字小文字無視 grep -ri "pattern" . rg -i "pattern"
行番号表示 grep -rn "pattern" . rg -n "pattern"
ファイル名のみ grep -rl "pattern" . rg -l "pattern"
ファイル種別指定 grep -r --include="*.py" "pattern" . rg -t py "pattern"
ディレクトリ除外 grep -r --exclude-dir=dist "pattern" . rg --ignore-file .gitignore "pattern"
コンテキスト表示 grep -r -C 3 "pattern" . rg -C 3 "pattern"

速度比較の目安

Linux カーネルのソースコード(約2.5万ファイル)での検索時間の目安:

ツール 検索時間(目安)
grep -r 約15〜30秒
ripgrep 約1〜3秒

ripgrep は特に大規模なコードベースで圧倒的な速度を発揮します。日常的に使う場合は ripgrep の導入を検討する価値があります。


まとめ

grep で下の階層のすべてのファイルを再帰検索するポイントをまとめます。

  • 基本は grep -r "パターン" . でカレントディレクトリ以下を再帰検索
  • --include でファイルを絞り込み、--exclude-dir で不要なディレクトリを除外
  • -n(行番号)、-l(ファイル名のみ)、-C N(コンテキスト)を組み合わせると見やすい
  • 大規模プロジェクトには ripgrep(rg) が高速で使いやすい
# 実務でよく使うパターン(まとめ)
grep -rn --include="*.py" --exclude-dir=".git" "pattern" .

関連記事