コンテンツにスキップ

検索と置換

pyfltrのgrep/replaceサブコマンドの使い方を扱う。 コーディングエージェント向けにはpyfltr mcpの grep/replace/replace_undo/replace_historyツールも公開する。

概要

  • pyfltr grep: 正規表現でファイル横断検索する。 pyfltr設定のexclude/extend-exclude/respect-gitignoreを尊重するためnode_modulesやbuild配下のノイズが混入しない
  • pyfltr replace: 横断置換する。書き込みが既定の動作であり、世代管理付きの--undoで取り消せる
  • 両者は共通オプション名(-i/-w/-x/-F/--type/-g等)を共有し、 grepで誤爆ゼロを確認した引数列をそのままreplaceへ切り替えられる

grep

grep基本形

pyfltr grep <pattern> [paths...] の形式で実行する。 pathsを省略するとカレントディレクトリ全体が対象となる。

例:

pyfltr grep "TODO" src/
pyfltr grep -i "deprecated" .
pyfltr grep -F "exact_string" docs/

grepオプション

  • -e/--regexp PATTERN: 追加パターン(複数指定可、OR結合)
  • -f/--file PATH: パターンファイル(1行1パターン)
  • -F/--fixed-strings: 固定文字列モード
  • -i/--ignore-case: 大文字小文字を区別しない
  • -S/--smart-case: 大文字を含まないパターンのみignore-caseを有効化
  • -w/--word-regexp: 単語境界マッチ
  • -x/--line-regexp: 行全体マッチ
  • -U/--multiline: マルチラインマッチ
  • -A/-B/-C N: 前後文脈
  • -m/--max-count N: ファイル単位の上限
  • --max-total N: 全体上限(暴発防止用、pyfltr独自)
  • --type TYPE: 言語タイプフィルタ(python/rust/ts/js/md/json/toml/yaml/shell)
  • -g/--glob PAT: globフィルタ
  • --encoding ENC: ファイル読み込みエンコーディング。UTF-8の復号に失敗した場合はCP932を試す
  • --max-filesize BYTES: ファイルサイズ上限
  • --max-preview-chars N: 返却する本文1件あたりの文字数上限(既定200、0で無制限)
  • --auto-summary/--no-auto-summary: 結果量に応じた自動省略の有効化・無効化
  • --no-exclude/--no-gitignore: pyfltr設定の無効化
  • --output-format text|json|jsonl: 出力形式

出力形式

  • text(既定): path:line:col:line_text 形式
  • json: 単一JSONとしてmatches配列とsummaryを返す
  • jsonl: 検索完了後にheader、検索結果、警告、summaryの順で出力

AI_AGENT / CODEX_CI / CLAUDECODE / CURSOR_AGENT環境変数のいずれかが設定されている場合はjsonlが既定値となる。

結果量に応じた自動省略

コーディングエージェント環境では--auto-summaryが既定値として有効となり、MCPのgrepでも同様に有効となる。 CLIを通常の端末から実行する場合は従来どおり全マッチを表示する。

自動省略は検索結果全体を取得してから、結果部分の直列化後の長さが10,000文字以内に収まる形式を選ぶ。 特定のファイル名や拡張子は判定に用いない。 1ファイルのヒットが多い場合は、まずそのファイルだけをfile_resultへまとめ、本文を省略して件数を返す。 低密度ファイルのマッチ本文は予算内で維持する。 それでも収まらない場合は、全ファイルの件数表示、各ファイルの代表マッチを均等に返す表示の順に縮約する。

output_modeは選択された形式を示し、full・mixed・grouped・counts・sampledのいずれかとなる。 summaryにはreturned_matches・omitted_matches・omitted_filesが含まれる。 省略が無い場合はoutput_mode: "full"となる。

-m・--max-total・--max-preview-chars・集計オプションを明示した場合は、その指定を優先して自動省略しない。 --no-auto-summaryでも全マッチ表示へ復元できる。 JSONLは検索結果をバッファリングし、出力形式を確定してからheader以降を出力する。

読み込みエンコーディング

既定のUTF-8読み込みに失敗したファイルは、厳密なCP932として再度読み込む。 CP932でも読み込めない場合は警告してそのファイルをスキップする。 --encodingでUTF-8以外を明示した場合は、指定したエンコーディングだけを使用する。

マッチ本文のプレビュー上限

grepが返す本文(マッチ行line_text・マッチ文字列match_text・前後コンテキスト)は、 1件あたり既定200文字までのプレビューとして返る。 minifiedファイルやsource mapのような巨大な単一行に一致した場合でも、 1件のマッチが応答全体を占有しない。 この上限はCLIのtext・json・jsonlとMCPのgrepツールへ同じ値で適用する。

検索・件数上限(-m/--max-total)・集計(-l/-c/--files-without-match)は 切り詰め前の全文を対象とするため、上限を変えてもマッチ件数と集計結果は変わらない。

--max-preview-chars=0を指定すると切り詰めを行わず、この上限を導入する前と同じ本文が返る。 返るのはsplitlines()が返す行本文であり、行末の改行文字は従来どおり含まない。

切り詰めが1件でも発生した実行では、実行方法を問わず警告を返す。 text形式はwarningsセクション、jsonl形式はkind:"warning"レコードとsummaryのwarnings件数、 json形式はpayloadのwarnings配列とsummaryのwarnings件数、MCPは戻り値のwarningsで受け取る。

json形式のwarnings要素はsourceとmsgを持ち、対処の手掛かりを伴う警告だけがhintを持つ。 jsonl形式のkind:"warning"レコードからkindを除いたキー集合と同じで、summaryのwarningsはその要素数を示す。 MCPのwarningsは文字列の配列で、対処を伴う警告は本文に続けて対処:以降に対処を含む。

json形式とjsonl形式のマッチには、切り詰めが発生した場合だけ次のキーが付く。

  • truncated: 切り詰めが発生したフィールド名の一覧(line_text・match_text・before・after)
  • line_text_offset: line_textを切り出した開始位置(0-origin文字数)。 行頭から切り出した場合は付かない

通常の文字から始まる一致では、line_textをマッチ開始位置を含む範囲で切り出すため、 行のどこに一致した場合でも一致箇所が範囲内に入る。 colは切り出し前の行における位置を示す。

マルチライン検索で一致が改行から始まる場合、line・colは正規表現の一致開始位置をそのまま示す。 line_textは行末の改行を含まないため、この場合のcolはline_textの行末直後を指し、 切り詰め時はcol - line_text_offsetがプレビュー長より1大きくなる。 実際に一致した改行と後続文字はmatch_textが保持する。

text形式は、マッチ行line_textを切り詰めた場合だけ本文の前へ切り出し開始位置を付ける。

src/app.min.js:1:20010:[+19909] ...(200文字のプレビュー)...

通常の文字から始まる一致では、colから[+N]の値を引くと、プレビュー内の位置(1-origin)が求まる。 マッチ行が上限以下の場合は従来どおりpath:line:col:line_textの形式で、 改行以外から始まる一致のcolはそのままline_text内の位置を指す。 マッチ文字列や前後の行だけが切り詰められた場合もマッチ行の表示は変わらず、切り詰めは警告で通知する。

grep→replace連携

全一致を出力したgrep結果のsummary(jsonl形式時)には、replaceへの切り替え案内が含まれる。 集約・省略されたJSONLはreplace --from-grepへ入力できない。 --no-auto-summaryを指定して対象を絞り込み、全一致のJSONLを保存してから置換内容を確認する。

replace

replace基本形

pyfltr replace <pattern> <replacement> [paths...] の形式で実行する。 書き込みが既定動作。--dry-run で試行できる。

例:

# 試行(書き込みなし)
pyfltr replace --dry-run "old_name" "new_name" src/
# 実書き込み(履歴に保存される)
pyfltr replace "old_name" "new_name" src/
# 直前のreplaceを取り消す
pyfltr replace --undo <replace_id>

replacementはre.sub互換で、\1/\g<name>によるキャプチャ参照ができる。

replaceオプション

  • grep側と共通: -F/-i/-S/-w/-x/-U/--type/-g/--encoding/--max-filesize/--no-exclude/--no-gitignore
  • replace固有:
    • --dry-run: 書き込みせず差分のみ表示
    • --show-changes: 各置換箇所の前後行を表示
    • --within ANCHOR: アンカー正規表現の行と前後(-A/-B/-C)で定まる領域内のみ置換する
    • -A/-B/-C N: --within領域の前後幅(--withinと併用必須)
    • --exclude-file PATH: 特定ファイルを置換対象から除外(複数指定可)
    • --from-grep PATH: grep出力JSONLを再入力し、対象ファイル集合を限定する
    • --undo [<replace_id>]: 過去のreplaceを取り消す(pattern位置に履歴IDを渡す)
    • --force: undo時のハッシュ不一致を無視して強制復元
    • --list-history: 保存済み履歴一覧を表示
    • --show-history <id>: 指定履歴の詳細を表示

ブロック内限定置換(--within)

--withinはアンカー正規表現にマッチした行と前後コンテキストで定まる領域内のみを置換対象とする。 領域幅は-A/-B/-Cで指定する。sedの範囲アドレスに相当し、特定ブロック外を変更せずに書き換えたいときに使う。

# 「[section]」を含む行の前後2行の範囲内だけ「old」を「new」へ置換する
pyfltr replace "old" "new" config.toml --within "\[section\]" -C 2
  • アンカーは検索側フラグ(-i/-w等)を共用する。アンカー専用のフラグは無い
  • --withinと-U/--multilineは併用できない(領域は行範囲で定めるため)
  • -A/-B/-Cは--withinと併用必須。--withinなしで指定するとエラーになる
  • 置換件数(count)は領域内で実置換した件数で、領域外のマッチは含めない

文字コードと改行

置換時の検索は、指定した文字コードで読み込んだCRLFと単独CRをLFとして扱う。 そのため-xの行全体一致と-Uの行頭・行末アンカーは、元の改行形式によらず同じ行へ一致する。 検索用の文字列にはCRが残らないため、正規表現の\rは元ファイルのCRへ一致しない。

置換範囲外の本文、改行、BOMは元のバイト列から保持する。 一致範囲に元の改行が含まれる場合は、その元改行も置換対象となる。 置換式に含む改行は、\n・\r\n・\rの指定どおり、対象の文字コードで書き込む。 キャプチャー参照で再利用する元の改行は、検索時のLFとして展開する。

undo(取り消し)

書き込み時に世代管理ディレクトリへ「変更前のバイト列・実際に書いたバイト列のハッシュ・各置換箇所の前後行」を保存する。 pyfltr replace --undo <replace_id> で取り消せる。 新しい履歴では、指定した文字コード、BOM、置換対象外の改行および末尾改行を保持し、取り消し時に置換前のバイト列へ戻す。

ファイルが手動編集されてバイト列のハッシュが一致しない場合は、改行やBOMだけの変更でも取り消しを全件中断して警告が出る。 意図的に強制復元する場合は --force を併用する。

旧形式の履歴も一覧・詳細・取り消しで利用できる。旧履歴は変更前の文字列だけを保持しており、 元の改行情報を復元できないため、取り消し時に完全なバイト復元を保証できない旨を警告する。 CLIでは既存の警告出力、MCPのreplace_undoではwarningsに通知する。

履歴の自動クリーンアップは世代数・合計サイズ・保存期間の3軸で行う。 設定キー(pyproject.tomlまたは pyfltr config set --global で指定):

  • replace-history-max-entries: 最大世代数(既定100件)
  • replace-history-max-size-bytes: 履歴全体の合計バイト数の上限。 既定値は200 * 1024 * 1024バイト(約200 MiB)
  • replace-history-max-age-days: 保存期間の上限(既定30日)

誤爆除外フロー

  1. pyfltr grep --no-auto-summary --output-format=jsonl ... > matches.jsonl でgrepの結果を保存
  2. matches.jsonlをエディタで開き、置換対象外のmatch行(のfileフィールド)を確認
  3. 不要ファイルを --exclude-file=path/to/file.py で個別除外するか、 matches.jsonl自体を編集して --from-grep=matches.jsonl で渡す

--from-grepで読み込むJSONLは、grep実行時のcwd相対でファイルパスを保存している。 このためreplaceを呼ぶときはgrep実行時と同じcwdから呼ぶ必要がある(cwd差で対象ファイルが 1件もマッチしなくなる事象を避けるため)。 複数プロジェクト横断や別ディレクトリからの呼び出しが必要な場合は、--exclude-fileで個別の 絶対パスを指定する運用へ切り替える。

--from-grepはoutput_modeがfullのJSONLと、output_mode導入前のJSONLだけを受理する。 自動省略されたJSONLは対象ファイルを網羅しない可能性があるため拒否する。 置換対象一覧として保存するgrepでは--no-auto-summaryを指定する。

マッチ単位除外(path:line単位)は当面スコープ外で、ファイル単位で十分な精度を狙う設計。

MCP公開ツール

検索・置換にはgrep・replace・replace_undo・replace_historyを使う。 サーバーが提供する全ツールと共通の応答の読み方はCLIコマンドのMCP参照を参照。

  • grep(paths, pattern=None, patterns=None, pattern_file=None, context=None, summary_mode=None, ...): ファイル横断検索。patternsは複数パターン、pattern_fileは1行1パターンのファイルを受け取り、 patternとOR条件で検索する。contextは前後文脈の一括指定に使う。 summary_modeはfiles_with_matches・count・files_without_matchのいずれかを受け取り、 マッチ明細を空にして対応する集計結果を返す。 files_without_matchでは全ファイルの確認が必要なため、正のmax_totalを併用できない max_totalを省略した場合も全マッチを走査し、auto_summary=Trueによる適応的な出力縮約で応答量を抑える。 max_preview_charsは返却する本文1件あたりの文字数上限(既定200、0で無制限)で、 切り詰めが発生した場合はmatches[].truncated・matches[].line_text_offsetと戻り値のwarningsで通知する
  • replace(pattern, replacement, paths, dry_run=True, within=None, from_grep=None, context=None, ...): 横断置換。from_grepはgrepのJSONL出力から対象ファイルを限定する。 contextはwithinで指定したアンカーの前後幅を一括指定する。 dry_runの既定値はTrue(CLI既定のFalseと異なり、LLM暴発防止)。 withinにアンカー正規表現を渡すと、before_context/after_contextで定まる領域内のみ置換する(CLIの--within相当)。 読み込めずにスキップしたファイルは、原因と対処をwarningsで通知する
  • replace_undo(replace_id, force=False): 取り消し。旧履歴の復元限界と、手動編集によるスキップ(force=Trueでの強制復元の案内)は応答のwarningsで通知する
  • replace_history(action, replace_id=None): action=listで履歴一覧、action=showで指定履歴の詳細を返す

LLMエージェントはreplaceを呼ぶ際、明示的にdry_run=Falseを指定しない限り実書き込みされない。 withinなしでbefore_context/after_context/contextを渡した場合と、 withinとmultilineを併用した場合は、いずれもエラーを返す。