搜索与导航在迷宫里精准找到目标
一个 50 万行的代码库里改一个函数——导航不熟,
你会漏改 5 处、改错 2 处、看不见的 1 处会在生产爆炸。
今天教 Claude 怎么用 Grep / Glob / Bash find 工具家族;
怎么用正则与路径模式精准过滤;怎么追踪函数定义、引用、调用链;
什么时候自己直接搜、什么时候让 Explore agent 替你做开放式探索。
思维导图
FIG M — DAY 07 KNOWLEDGE MAP · CODEBASE NAVIGATION TOOLKIT
搜索工具家族
Claude Code 把搜索拆成三件功能不同的工具——
Grep 找内容、Glob 找路径、Bash 兜底什么都能干。
用对工具是导航的第一性原理:用错了不仅慢,还会找不全。
三件工具的职责分工
| 工具 | 职责 | 典型查询 | 不擅长 |
|---|---|---|---|
| Grep | 在文件内容里搜正则模式 | 找所有 useState 用法 | 路径模糊查找 |
| Glob | 按路径模式列出文件 | 列出所有 **/*.test.ts | 查文件内容 |
| Bash find | 带条件组合的文件搜索 | "7 天内修改 + 大于 100KB" | 跨文件内容关联 |
| Read | 精读已知路径的具体内容 | 看 src/auth/login.ts 第 50–80 行 | 开放式查找 |
| Explore agent | 开放式跨文件探索(子代理) | "权限验证逻辑都在哪儿?" | 精确定位 |
Grep 关键参数
# 1. 最简单 — 找出现 useState 的所有文件 Grep pattern: "useState" # 2. 限定路径 + 文件类型 Grep pattern: "useState", glob: "src/**/*.tsx" # 3. 看上下文 (前 2 行 + 后 5 行) Grep pattern: "useState", -B: 2, -A: 5, output_mode: "content" # 4. 大小写不敏感 + 限制结果数 Grep pattern: "todo", -i: true, head_limit: 50 # 5. 多行匹配 (跨换行的 import) Grep pattern: "import\\s+\\{[\\s\\S]*?Logger", multiline: true
三种 output_mode
files_with_matches
默认模式 — 只列匹配的文件路径。最省 token。当你只想知道"有哪些文件涉及 X"时用这个。
content
列出每条匹配的具体行(像 grep 默认行为)。配合 -A / -B 参数看上下文。需要看代码细节时用。
count
每个文件的匹配数量。用于评估"哪个文件用得最多"——决定下一步精读哪一个。
Glob 路径模式
# Glob 支持双星号递归与逗号分组 Glob pattern: "src/**/*.ts" # src 下所有 ts Glob pattern: "**/*.{test,spec}.ts" # 所有 test/spec 文件 Glob pattern: "src/!(node_modules)/**/*.ts" # 排除 node_modules Glob pattern: "docs/*.md" # 仅 docs 直接子文件 # 结果按 mtime 倒序 — 最近改的优先 # 结果数太多时,先看最近的几个通常足够
模式与正则技巧
"搜索找不到"——99% 是模式写错了,不是它不存在。 下面是把 Grep 用准的 5 个常见技巧。
常见陷阱
没加单词边界
user 会匹配到 username / user_id / UserService——你可能只想要独立的 user。用 \\buser\\b 锚定单词边界。
特殊字符没转义
user.name 中的 . 是正则通配符——会匹配 user_name 等。要找字面点必须 user\\.name。
多行模式忘开
跨行的 import {\\n A,\\n B\\n} 单行模式匹配不到。涉及跨行结构必须 multiline: true + 用 [\\s\\S] 替代 .。
大小写默认敏感
找 TODO 时 todo / Todo 都漏掉。加 -i(case insensitive)。
高频正则模板
# 1. 函数定义(JS/TS) "^(export\\s+)?(async\\s+)?function\\s+myFunc\\b" "const\\s+myFunc\\s*=\\s*(async\\s*)?\\(" # 箭头函数 # 2. 类方法定义(无 function 关键字) "^\\s+(async\\s+)?myMethod\\s*\\(" # 3. import 该模块 "from\\s+['\"][^'\"]*myModule['\"]" # 4. 函数调用(排除定义) "\\bmyFunc\\s*\\(" + 配合 -v 排除定义文件 # 5. TODO / FIXME / XXX 三类待办 "\\b(TODO|FIXME|XXX|HACK)\\b" + -i # 6. 跨行 try-catch 块 "try\\s*\\{[\\s\\S]*?\\}\\s*catch" + multiline # 7. 类型定义(TS interface / type) "^(export\\s+)?(interface|type)\\s+UserProfile\\b"
两步搜索 — 缩小范围
大型仓库一次搜可能命中几千行——分两步收敛比一次搞定更高效。
# 第 1 步: 找出涉及 X 的文件(files_with_matches) Grep pattern: "PaymentService", output_mode: "files_with_matches" # 输出: 17 个文件 # 第 2 步: 在这些文件里精确找定义 Grep pattern: "^(export\\s+)?class\\s+PaymentService\\b", glob: "src/payments/**", output_mode: "content" # 输出: 1 处定义 + 文件路径,可直接跳到 Read
find 兜底场景
# Grep / Glob 不擅长的:基于 metadata 的查找 # 1. 最近 1 小时改过的文件 $ find . -type f -mmin -60 -not -path './node_modules/*' # 2. 大于 1MB 的源码文件(找该拆的大文件) $ find src -type f -size +1M -name '*.ts' # 3. 空文件 / 只有声明没实现的文件 $ find src -type f -size -100c # 4. 软链 / 死链 $ find . -type l -! -exec test -e {} \\; -print # 5. 修改时间组合 — 上周改过 + 今周没动 $ find . -mtime -14 -mtime +7 -name '*.go'
符号定义与跨文件追踪
"改一个函数前要先看谁在调它"——这是影响面分析的核心。 没有 LSP 的纯文本搜索做不到 100% 精确,但三步法可以让你在 2 分钟内 画出函数的完整使用图。
三步法 · 找完整影响面
FIG 03 · IMPACT ANALYSIS — DEFINE → CALLERS → CONTEXT
实战:重命名 getUser
# Step 1 — 找定义 Grep pattern: "^(export\\s+)?(async\\s+)?function\\s+getUser\\b", output_mode: "content" # 输出: src/auth/user.ts:42 — function getUser(id: string) # Step 2 — 找 callers (调用 + 排除定义文件) Grep pattern: "\\bgetUser\\s*\\(", output_mode: "content", -n: true # 输出 23 处调用 — 含定义本身,需手动排除 # Step 3 — 对每个 caller 取上下文 Grep pattern: "\\bgetUser\\s*\\(", -A: 3, -B: 1, output_mode: "content" # 看 callers 在做什么 — 有的是测试,有的是 fallback # Step 4 — 找类型流转(被赋给什么类型变量) Grep pattern: "const\\s+\\w+\\s*=\\s*await\\s+getUser" # 看返回值都被怎么消费
import / export 关系图
# 找谁在 import 这个模块 Grep pattern: "from\\s+['\"][^'\"]*payments/index['\"]" # 找这个模块 export 了哪些符号 Grep pattern: "^export\\s+(const|function|class|type|interface)", glob: "src/payments/index.ts", output_mode: "content" # 找循环依赖嫌疑(A 引 B + B 引 A) Grep pattern: "from\\s+['\"]./auth", glob: "src/user/**" Grep pattern: "from\\s+['\"]./user", glob: "src/auth/**" # 两个都有命中 → 大概率循环依赖
跨语言查找速查表
| 目标 | JS / TS | Python | Go |
|---|---|---|---|
| 函数定义 | function\\s+name\\b | def\\s+name\\b | func\\s+(\\w+\\s+)?name\\b |
| 类定义 | class\\s+Name\\b | class\\s+Name\\b | type\\s+Name\\s+struct |
| 导入语句 | from\\s+['\"][^'\"]*name | (from|import)\\s+name | ['\"][^'\"]*name['\"] |
| 方法调用 | \\.name\\s*\\( | \\.name\\s*\\( | \\.name\\s*\\( |
| 关键差异 | JS/TS 有 const = () 箭头函数变体 · Python 装饰器要单独搜 @name · Go 接收器要 func (r *T) | ||
Explore agent — 何时让分身去找
自己直接搜适合已知关键词的精确查找。
一旦目标模糊("权限验证逻辑都在哪?"、"哪些地方处理了 race condition?")
或者要查 5+ 处不同方向——把任务交给 Explore agent 更高效。
直搜 vs 派 Explore 决策表
| 场景 | 直接搜 | 派 Explore agent |
|---|---|---|
| 已知函数名 / 类名 | ✓ Grep 一发命中 | ✗ 杀鸡用牛刀 |
| 查 1-2 处定义/引用 | ✓ 三步法即可 | ✗ 不必要 |
| "哪儿做了 X?"模糊语义 | ✗ 关键词不全 | ✓ 它会试多个查询 |
| 5+ 处分散在多目录 | ✗ 主上下文塞不下 | ✓ subagent 摘要返回 |
| 需要看完整文件再判断 | ✗ 主会话被污染 | ✓ 子代理只回结论 |
| 代码评审 | ✗ 用专门的 code-reviewer agent (Day 14) | |
Explore agent 调用模板
# 在 Claude Code 主会话里,可以这样让它调 Explore: > 用 Explore agent 帮我找一下: 权限验证逻辑都分布在哪些文件里? 是用中间件还是装饰器? 搜索广度: medium # Claude 会调起 subagent_type=Explore 的子代理 # 子代理在隔离上下文里跑多次 Grep / Glob / Read # 主会话只收到一份总结报告
三档广度参数
单点查找
1-2 次搜索就够了。比如"authMiddleware 在哪定义?"——但你不太确定关键词。
中等探索
3-5 次搜索,试几个不同查询。"权限验证都在哪?"这种模糊问题。
多角度全面查
跨多个命名风格 / 多个目录约定。"哪些地方做了 retry 逻辑?"——可能叫 retry / again / backoff / repeat。
Explore 不该做的事
代码评审 / 审计
Explore 是定位工具不是判断工具。代码质量、设计审查用 code-reviewer agent (Day 14)。
跨文件一致性检查
"全项目命名风格是否一致"——它读的是片段不是完整文件,容易遗漏。这种交给主会话+ Read 全文。
编辑 / 修改文件
Explore agent 没有 Edit 权限——只读探索。改代码必须回到主会话。
开放式设计问题
"如何重构这个模块"是架构判断,不是定位。用 Plan agent (Day 15)。
主会话 vs Explore 的上下文边界
FIG 04 · CONTEXT ISOLATION — EXPLORE 干脏活,主会话只看摘要
大型代码库的导航策略
50 万行 monorepo 的导航和 5000 行小项目完全不同—— 工具链一样,但策略必须升级。
5 条经验法则
- 先拓扑后内容——大库先用 Glob 摸目录结构(
**/package.json看子项目分布),建立心智模型再下手 Grep - 用 monorepo workspace 边界——只搜你关心的 workspace,
apps/web/**/*.ts比**/*.ts快 10 倍且更准 - 排除生成代码——
dist/build/.next/node_modules默认忽略,但__generated__/*.pb.go容易混入 - 分层搜索——先在
src/找业务逻辑,再到packages/找共享库,最后看infra/配置层 - 记住"形状"而非"内容"——大库要记"这类逻辑通常在哪个目录约定下"(如
**/services/*Service.ts),命中率比记具体名字高
Token 预算管理
head_limit 控量
查"找一下 useState 用法"时加 head_limit: 30——前 30 个例子足以判断模式,不需要 800 行全量。
files_with_matches 优先
开放探索阶段先用这个 mode 看分布,只在确定要精读时才换 content。前者 token 是后者的 1/20。
Read 时指定行范围
已知第 300 行附近有目标,用 Read(file, offset: 280, limit: 60)——别 Read 整个 2000 行文件。
大查询交给 Explore
预计要读 10+ 文件才能回答的问题——直接派 Explore agent,主会话只承担最终决策。
缓存与思维模型
第二次找同一类东西时,不要再搜一遍——把上次的结论记下来。 Day 12 的 Memory 系统是天然的代码库导航 cache:
# 第一次摸清楚后,存成 reference 类 memory: > 记一下: 项目的认证逻辑分布在 - src/auth/middleware/ — Express 中间件 - src/services/auth.ts — 业务层 - packages/shared/jwt.ts — 共享 JWT 工具 下次问"权限"相关问题先看这三个位置,再扩展搜索 # Claude 会保存为 reference memory # 之后再问类似问题,Claude 直接定位,无需重新探索
Labs
四个递进的实验——从最基础的 Grep 用法到完整的影响面分析。 预计 50 分钟,全部在你最熟的项目里完成,效果最好。
三种 output_mode 对比
选一个项目里频繁出现的关键词(如 useState / logger)。分别用 files_with_matches / content / count 三种 mode 跑一次。对比结果与各自的 token 消耗,体会"该用哪种"。
正则陷阱排雷
挑一个常见单词(如 user / id)。先不带边界搜——记下结果数;再加 \\b 边界重搜——结果数差几倍?最后加 -i 看大小写差异。把这次的"差异"写进 memory 当 feedback。
影响面三步法实战
选一个项目里你想重命名的函数。按"定位定义 → 列 callers → 看 caller 上下文"完整跑一遍。最后画一个简单 ASCII 图列出 callers 分类(测试 / 生产 / 已废弃)。
Explore 派活实战
给 Explore agent 一个模糊问题: "项目里所有处理 retry / 重试逻辑的位置, 它们用了什么策略 (固定间隔/指数退避/jitter)?"——观察它探索多少次、用了哪些关键词、最终返回的摘要质量。把好的查询记下来。
常见问题
Q · 01
Grep 工具和直接 bash grep / rg 有什么区别?
+
底层都基于 ripgrep(rg),输出能力一致。区别在权限与上下文管理: (1) Grep 工具是 Claude Code 的原生工具,自动遵循项目的 .gitignore / 隐藏文件规则,Bash 的 grep 不一定有这些默认;(2) Grep 工具的输出会被 Claude Code 结构化解析,token 占用更可控,Bash 输出全是原始文本;(3) Grep 工具有 multiline、output_mode 等专门为 LLM 优化的参数。
选择: 能用 Grep 工具就别用 Bash grep——除非你需要 Bash 的管道组合(如 rg ... | sort | uniq -c)。
Q · 02 为什么 Glob 找的文件顺序看起来很乱? +
不是乱——是按 mtime 倒序。最近修改的在前,远古文件在后。这是为 LLM 调试场景优化的:你大概率关心"最近改动了什么"。
如果想要按字母序,在 head_limit 之后用 Bash sort 处理。但通常按时间序更有用——能直接看到"哪些文件最近被动过",对应近期的功能或 bug。
Q · 03 查 callers 时找到的"假阳性"太多怎么办? +
纯文本搜索的固有限制——它无法分辨同名变量。三个收敛技巧: (1) 加调用语法: \\bgetUser\\s*\\( 比纯 getUser 准很多,因为只匹配后跟左括号的;(2) 限定 import 来源: 先 Grep from ['"]\\./auth/user 找到所有引入文件,再在这些文件里搜调用;(3) 排除已知误报路径: glob: "src/**/*.ts" + 排除 **/*.test.ts 减少测试噪音。
真要 100% 精确——只能上 LSP 工具(如 typescript-language-server),纯文本永远是逼近不是精确。但 95% 准对 99% 场景已经足够。
Q · 04 Explore agent 返回的结果不靠谱——经常漏掉关键文件? +
Explore 的局限是它读片段不读全文——某些跨文件信息会漏。三个改善方法: (1) 把广度调到 thorough——让它多试几个关键词;(2) 给它命名提示: "可能叫 retry / backoff / again / repeat,试这几个";(3) 分两步: 第一次 Explore 拿主线索,主会话用 Read 看完整文件确认。
对"我不能漏一个"的场景(比如重构前找所有调用),不要单靠 Explore——必须用三步法手动跑一遍 Grep。Explore 适合"知道大概有多少"不适合"必须找全"。
Q · 05 大型 monorepo 里搜索奇慢——怎么提速? +
三个优化方向: (1) 用 glob 锁定 workspace: apps/web/**/*.ts 而非全量 **/*.ts——大多数搜索不该跨整个 monorepo;(2) type 过滤: type: "ts" 比 glob: "**/*.ts" 略快(rg 内部优化);(3) 排除大目录: 在项目根加 .ignore 文件列出 __generated__/ / vendor/ / fixtures/ 这类大但少看的目录,Grep 自动遵守。
另外: 第一次搜索后把"形状"记下来(Day 12 Memory)——下次直接定位,不要重新全量搜。
Q · 06 何时该让 Claude 自己探索, 何时该自己写好 Grep 让它执行? +
分水岭是"你能否一句话写清楚关键词"。能——直接给 prompt: "用 Grep 找 useState 在 src/components/ 下的所有用法",最准最快;不能——把意图给 Claude 让它自己选关键词,或派 Explore agent。
有个反直觉的事实: 有经验的开发者亲手写 Grep 几乎总是更快。Claude 不知道你项目里"权限逻辑通常以 ensureXxx 开头"这类隐性约定,而你知道。所以不熟的代码库让 Claude/Explore 探,熟的代码库你直接给精确查询——把 Claude 用作执行器而不是探索器。
复习题
- Grep / Glob / Bash find / Read / Explore agent 五种工具的职责分工是什么? 给一个"该用哪个"的判断口诀。
- "两步搜索"指什么? 它在 token 预算上有什么优势?
- 查函数 callers 时为什么要用
\\bname\\s*\\(而不是纯关键字name? - 什么场景下应该派 Explore agent? 什么场景下反而不该用它?
- 大型 monorepo 搜索慢——给出至少 3 条提速策略。
自检清单
- 能根据问题在 Grep / Glob / Bash find / Read / Explore 之间正确选工具
- 能熟练使用 Grep 的 output_mode / multiline / -A -B / glob / type 参数
- 掌握"两步搜索":先 files_with_matches 探规模,再 content 精读
- 会用
\\b单词边界 /-i大小写不敏感避开常见正则陷阱 - 能跑通"定位定义 → 列 callers → 看 caller 上下文"三步法
- 对 JS/TS/Python/Go 都能写出函数定义 / 类定义 / 导入语句的常用正则
- 会判断何时该派 Explore agent,何时直接搜更高效
- 建立了"先记形状再记内容"的 monorepo 导航习惯
延伸阅读
Claude Code — Search Tools
官方 Grep / Glob / Bash / Read 工具完整参数文档,以及它们的搜索语义与权限规则。
ripgrep User Guide
Grep 工具底层基于 ripgrep——掌握 rg 的高级语法(PCRE2 / Unicode / smart-case)能让搜索能力更强。
Codebase Navigation Patterns
大型项目里通用的导航模式集——目录约定、命名前缀、按层分搜、impact analysis 范式。
Day 08 预告
多文件操作与任务追踪 — 批量重构 · TodoWrite · 大型变更管理
今天你学会了精准定位——明天教 Claude 怎么同时改 17 个文件而不出错。 MultiEdit 同文件多点编辑、跨文件批量重构的安全模式、用 TodoWrite 把大型变更切成可恢复的步骤、 失败时如何从中间状态继续而不重来。 改一个函数和改一整片相关函数,所需的工程纪律完全不同。