TechQuant Blog

ripgrep(rg)を使い倒すための実践テクニック — grepを卒業する日常コマンド集

7分で読める

ある朝、20万行ほどあるログと auto_daily_trader のソース一式から「notify を呼んでいる箇所」を grep -r で漁ろうとして、Raspberry Pi 5 のファンが唸り出した。22秒待って帰ってきた結果は、.git/node_modules/ のノイズでほぼ埋まっていた。もう嫌になって ripgrep(以下 rg) を入れたら、同じ検索が 0.4 秒で終わった。それ以来、grep はほとんど打たなくなった。

ただ、速いだけのツールではない。オプションを把握すると、grep では面倒だったことが一気に片付く。この記事は自分が実運用で毎日叩いている rg の使い方を、順不同で並べたメモのようなものだ。

対象は rg を入れたばかり、もしくは grep の癖が抜けていない人。インストールは apt install ripgrepbrew install ripgrep で入る。

なぜ ripgrep なのか

速度は枕詞みたいなもので、本質はそこじゃない。rg がデフォルトで気が利くポイントを先に並べておく。

  • .gitignore を自動で尊重する(node_modules/ を探しに行かない)
  • バイナリファイルをスキップする
  • 隠しファイルをデフォルトで無視する
  • PCRE2 サポートで後方参照や先読みが使える(-P)
  • UTF-8 まわりが素直で、日本語ソースでも化けにくい

grep -r の最大の欠点は「何でも拾ってしまう」ことだった。.git/objects/ の中のバイナリを舐めて進みが止まる、あの光景を見なくて済むだけでも、乗り換える価値がある。

毎日叩く基本オプション

単純検索

rg "notify_slack"

カレントディレクトリ以下を再帰検索。引数なしでこれ。grep -rn を打ち込んでいた指を止めて rg と打つクセを付けると、半日で馴染む。

大文字小文字をゆるく扱う

rg -i "todo"
rg -S "Notify"

-i は完全に無視。-S(smart case) はパターンに大文字が含まれていたらケースセンシティブ、含まれていなければインセンシティブになる。賢いので -S.ripgreprc に入れて常時オン運用している。

ファイル種別で絞る

rg "async def" -tpy
rg "useEffect" -tjs
rg "FROM" -tsql

-t はタイプフィルタ。rg --type-list で利用可能な種別が一覧できる。Python と Go のコードが混在するリポジトリで、片方だけ見たい時に刺さる。

ファイル種別を除外する

rg "TODO" -Tmarkdown -Tjson

大文字 -T は exclude。ドキュメント・ロックファイル系を一気に除けるので、コードだけ見たい時に重宝する。

行番号・前後コンテキスト

rg "class User" -A 5 -B 2

-A で後 N 行、-B で前 N 行、-C で両方。git blame に繋ぐ前の当たり付けでよく使う。

少し踏み込んだ使い方

glob でパスを絞る

rg "TODO" -g "*.py" -g "!tests/**"
rg "API_KEY" -g "**/config/*"

-g で glob パターン。! プレフィクスで除外。-t では表現しきれない、パス単位の絞り込みに使う。tests/ 配下のダミー API_KEY が結果を汚染する、みたいなやつをピンポイントで切れる。

隠しファイルも対象に

rg "token" --hidden
rg "token" -uu  # .gitignore も無視

.env.github/ の中も見たい時は --hidden。さらに -uu.gitignore の除外も解く。ビルド成果物の中に何か埋まっている気がする、みたいな探偵モードで使う。

複数行にまたがる検索

rg -U "fn\s+\w+\s*\([^)]*\)\s*\{" -tpy

-U でマルチライン。関数シグネチャや複数行の SQL を拾いたい時に出番がある。grep で同じことをやる不自由さから解放される瞬間だ。

ファイル名だけ出す・カウントだけ出す

rg -l "import pandas"
rg -c "ERROR" logs/

-l は list(該当ファイルのみ)、-c は count(ファイルごとの件数)。-c は障害対応中にエラーがどのログにどれだけ出ているか、一発で俯瞰したい時に重宝する。

JSON 出力で集計に回す

rg "panic:" --json logs/ | jq 'select(.type=="match") | .data.path.text'

--json で構造化出力。jq に流して集計したい時に便利。以前書いたjournalctl 実践ガイドでも触れたが、この「テキストを JSON にして jq で料理する」パターンは、CLI 運用の筋肉として磨いておくと汎用性が高い。

置換・ドライラン

表示上の置換を試す

rg "old_name" -r "new_name"

-r は replacement。ファイルは書き換えず、画面に置換結果だけ出す。sed -i でいきなり書き潰す前に「どう変わるか」を目視する用途で、自分は必ずこの一段を挟む。

実際に書き換えるなら、rg -l で対象ファイルを絞ってから sed -i に食わせる二段構えが事故が少ない。

rg -l "old_name" | xargs sed -i 's/old_name/new_name/g'

設定ファイル .ripgreprc で日常を整える

毎回 -S --hidden --smart-case を打つのは無駄なので、設定ファイルに寄せる。~/.ripgreprc を作ってこう書く。

# ~/.ripgreprc
--smart-case
--hidden
--glob=!.git/*
--glob=!node_modules/*
--glob=!.venv/*
--glob=!__pycache__/*
--colors=line:fg:yellow
--colors=path:fg:green
--colors=match:fg:red,style:bold
--max-columns=200
--max-columns-preview

そして ~/.bashrc~/.zshrc で環境変数を指定する。

export RIPGREP_CONFIG_PATH="$HOME/.ripgreprc"

.git/ を除外するのは-uu を使う時の安全装置。max-columns-preview は長い行(minify された JS とか)で検索結果が画面を埋め尽くすのを防ぐ。この一行を入れてから、うっかり minified ファイルに当たって端末が固まる事故が消えた。

エディタ・他ツールとの連携

fzf と組み合わせる

プロジェクト内のインタラクティブ検索は、rgfzf の二段で一気に化ける。

rg --line-number --no-heading --color=always "" \
  | fzf --ansi --delimiter=: \
        --preview 'bat --color=always {1} --highlight-line {2}' \
        --bind 'enter:become(nvim {1} +{2})'

入力しながら絞り込み、enter で該当行に飛ぶ。一度手に馴染むと戻れない。batcat の代わりに入れておくとプレビューがシンタックスハイライト付きで見える。

Neovim / VSCode

Neovim の telescope.nvimfzf-lua も、内部で rg を呼んでいる。VSCode は設定で検索バックエンドを切り替えられないが、.ripgreprc の設定は拡張機能が尊重してくれる場合がある(環境による)。いずれにせよ、rg が入っていれば各エディタの「プロジェクト検索」が速くなるのは共通だ。

ハマったポイント

.gitignore に入っているファイルが検索されない

本来の挙動だが、「なんでヒットしないの」と焦ることがある。-uu--no-ignore を付ける。そもそも .gitignore に意図しないパターンが入っていた、というオチも何度か経験した。

リモート SSH 越しにカラーが化ける

pager を通すと ANSI が壊れることがある。--color=alwaysless -R の組み合わせを覚えておく。rg "pat" --color=always | less -R

シンボリックリンクを辿らない

デフォルトでは辿らないので、必要なら -L。ただし循環参照で無限ループになる構造があると固まるので、--max-depth を併用するのが無難だ。

リモートの本番ログを grep しに行く運用をしているなら、踏み台サーバーを持っておくと楽になる。自分は軽量な VPS を一台挟んで、そこで rg を走らせる運用に落ち着いた。月額数百円のお名前.comの高性能VPS 程度でも、ログ集約と rg による横断検索には十分だ。

失敗談: 本番ログを舐めて容量を食い潰した話

手癖で rg "ERROR" /var/log/ を叩いたら、journal の巨大なバイナリを一部テキスト誤認で拾ってしまい、出力を tee で手元のファイルに落としていた関係で、Raspberry Pi の microSD に 3GB 級の出力が書き出された。rg はバイナリを基本スキップするが、圧縮・非標準形式のファイルで誤爆することがある。

以降、本番ログに対しては rg --max-filesize 100M のような上限を常に付けるようになった。エッジデバイスで雑にコマンドを打つのは危ない、という当たり前の反省だが、染みて覚えた。同じような運用の工夫はRaspberry Pi 5 をホームサーバーにする Tipsにもいくつか書いた。

もう一歩

さらに踏み込むなら ast-grep(構文木ベースの検索) と comby(構造的置換) が面白い。rg は正規表現の範囲で戦うツールなので、「関数呼び出しの第二引数だけ」みたいな構文寄りのクエリは別系統が要る。ただ、日常のほとんどは rg で足りる。まずここを使い倒すのが近道だ。

普段触っている CLI ツールのちょっとした整理は DevTools にまとめているので、よければ覗いてみてほしい。

結び

rg の価値は「速さ」ではなく「邪魔なファイルを自動で避けてくれる」ところにある。grep の腕力で戦っていた時代の小技(--exclude-dir=node_modules を毎回書く、みたいな)が全部不要になる。これだけで脳のリソースが解放される。

ここに書いたのは、自分の手元で本当に毎日叩いているものだけ。.ripgreprcfzf 連携の2つを整えれば、多分もう grep には戻れない。戻らなくていい。