50+ 处路径引用,怎么一口气全部找到并更新?

2026-06-06

背景

我用 ~/workspace/ 作为所有项目的统一工作目录。每个项目有独立的子目录加 .hermes-status/ 进度跟踪。这个结构用了一段时间,但有一个项目从最开始就放在了外面——公众号博客项目,在 ~/ruite-ai-practice/

当初没想那么多,直接就在家目录下建了项目。后来 workspace 结构成熟了,就产生了一个问题:怎么把它移进去,同时确保所有引用旧路径的地方都跟着更新?

这个项目有大约 50 处硬编码路径引用,分散在外部脚本、项目内脚本、技能文档三个层级。手动改容易遗漏,少改一处就可能让某个服务在运行时找不到路径。

方法论:审计 → 分类 → 执行 → 验证

这次迁移不是 mv + sed 一键搞定,而是按一套方法分步做的。

第一步:审计——发现所有引用

不依赖「我记得有哪些文件」,而是系统化搜索。分五个维度搜索:

# 外部工具脚本
grep -r "ruite-ai-practice" ~/tools/
grep -r "ruite-ai-practice" ~/.hermes/scripts/

# 项目内部脚本
grep -rn "ruite-ai-practice" ~/ruite-ai-practice/scripts/ --include="*.py"

# 技能文档
grep -r "ruite-ai-practice" ~/.hermes/skills/ --include="*.md"

# 配置文件
grep -r "ruite-ai-practice" ~/.hermes/config.yaml

# 别忘了搜索绝对路径形式
grep -rn "/home/liu" ~/ruite-ai-practice/scripts/ --include="*.py"

搜索 ~/ruite-ai-practice/ 是不够的——有些地方写的是 /home/liu/ruite-ai-practice/,还有些是 Path.home() / "ruite-ai-practice"。三种形式都要搜。

第二步:分类——哪些需要改?

把所有引用列出来后,按类型判断是否需要修改:

  • 使用 Path(__file__).resolve().parent.parent 的相对路径 → 自动适配,不用改
  • Path.home() / "ruite-ai-practice" → 需要改路径
  • /home/liu/ruite-ai-practice/(绝对路径)→ 需要改
  • 项目内 .venv-pelican/node_modules/ → 随目录移动,不用改
  • Git remote、Cloudflare Pages → 远程配置,不变

分类的收益是很大的:你不需要在 50 处引用上花同样的精力。大约一半是随目录移动自动解决,剩下的一半才需要手动改。

第三步:按优先级执行

优先级按”改了之后会不会立即影响在跑的服务”来排:

  1. 核心运行脚本(改了立即影响服务)
  2. pipeline server:WORK_DIRPIPELINE_SCRIPT 两处
  3. ntfy listener:CREATE_PIPELINETOPIC_MANAGER 两处
  4. webhook 订阅:prompt 中的 cd 路径

  5. 项目内脚本(改了影响下次构建)

  6. create-pipeline.py 中的 REPO_DIR
  7. t5-update-feishu.py 中的 os.chdir()
  8. gen_sensenova_images.py 中的 base_dir

  9. 技能文档(改了影响下次使用)

  10. 5 个技能文件 + references,约 30 处引用

  11. Memory 和状态文档(冷了再改也不迟)

每改完一个阶段验证一次,不等到最后再统一验证。

第四步:验证

验证清单是逐层展开的:

# 1. 验证核心服务
curl http://localhost:8645/health
# → {"status": "ok"}

# 2. 验证所有对外引用已更新
grep "PIPELINE_SCRIPT\|WORK_DIR\|CREATE_PIPELINE\|TOPIC_MANAGER" \
  ~/tools/rhook-pipeline-server.py ~/.hermes/scripts/ntfy-pipeline-listener.py

# 3. 验证 Git 工作正常
git status

五个踩坑记录

1. 绝对路径不止一种写法

你以为搜 ruite-ai-practice 就能找到所有引用。但有些地方写的是 /home/liu/ruite-ai-practice/,有的是 Path.home() / "ruite-ai-practice"。只搜关键词会漏掉那些用了 Path.home()os.path.expanduser() 的路径。

解决方法:结合两种搜索——既搜项目名,也搜 /home/liu

2. 技能文档的引用模式不统一

同一个技能文档里,有的引用是 ~/ruite-ai-practice/,有的是 /home/liu/ruite-ai-practice/,有的是相对路径。直接搜索替换不够精确——每个引用需要审查上下文判断是否要改。

最稳妥的做法:列出所有文件,逐文件审查每个匹配,确认上下文后再改。

3. 入口脚本用绝对路径还是 Path.home()?

Path.home() 在不同环境(cron、SSH、agent)下的行为可能不同。如果你写的脚本可能被多种方式调用,用 Path.home() 更安全。之后如果再迁移,只需要改一处配置或环境变量。

这次 ntfy-pipeline-listener.py 就改成了 Path.home() / "workspace" / "ruite-ai-practice" 而非绝对路径,就是为了不同环境都能正确解析。

4. 运行时服务的中断

改完脚本不等于新路径生效。如果你改了正在运行的脚本,而没有重启进程,运行时仍在用旧路径。

迁移过程中 pipeline serverntfy-bridge 需要重启。改完核心脚本后,要检查并重启相关进程:

ps aux | grep <进程名>
# 杀旧进程 → 启动新进程

5. 旧目录的删除时机

确定所有引用更新完毕、新路径稳定运行之前,不要删旧目录。mv 本身很快(同一文件系统上只是 inode 操作),但 CI/CD 和 cron 任务可能在移动期间执行。旧目录就是你的回退方案。

建议:新路径运行至少 24 小时,确认没有问题后,再清理旧目录。

这套方法不只是搬目录

回顾这次迁移,最有价值的不只是把项目移到了 workspace 下,而是沉淀了一套通用迁移方法:

  1. 系统化审计 — 不单纯搜索一个关键词,而是分维度搜索(脚本、配置、文档)
  2. 按风险分阶段 — 先改运行时关键路径,后改冷存储(文档)
  3. 验证链 — 每阶段完成后验证,不等到最后一并验证
  4. 回退方案 — 旧路径保留直到新路径稳定运行

这套方法可以用在任何需要全局更新的场景:域名更换、服务器迁移、证书更新、数据库连接串更换。任何涉及「多个分散引用指向同一个资源」的问题,都可以用这套流程。

下次遇到类似的迁移,不再需要从头想方法论了。