CLI AI

在项目所有文件中查找并替换

2026-05-03

命令是 grep -rl 'old' . | xargs sed -i 's/old/new/g'。重要的是前半部分:grep -rl 先列出会被改动的文件,这样你能在 sed 就地重写之前先看一眼。

1. 哪些文件会被改动?

clai
$ clai 列出 proj 下包含 old_name 的文件→ grep -rl 'old_name' proj/proj/src/b.pyproj/src/a.py

-r 递归查找,-l 打印文件名而不是匹配的行。两个文件——这是个可信的数字。如果显示 4000,说明你的模式太宽泛,正好帮你省下一下午的麻烦。

2. 只在这些文件里替换

clai
$ clai 把 proj 下所有包含 old_name 的文件里的 old_name 替换成 new_name→ grep -rl 'old_name' proj/ | xargs sed -i 's/old_name/new_name/g'$ cat proj/src/a.pynew_name = 1

只有匹配的文件会被打开。如果直接对整个目录树跑 sed -i,会重写仓库里每个文件的修改时间,包括那些根本没变的成千上万个文件。

3. 文件名带空格也不会出问题

clai
$ clai 同样的替换,但要能安全处理带空格的文件名→ grep -rlZ 'old_name' proj/ | xargs -0 -r sed -i 's/old_name/new_name/g'

-Z 让 grep 用空字节分隔文件名,-0 让 xargs 按空字节解析。少了这一对,my file.py 会被当成两个参数传进去,sed 一个都改不了。-r 则避免了没有匹配时 xargs 在没有参数的情况下运行 sed。

4. 跳过永远不想动的目录

clai
$ clai 替换时跳过 node_modules、.git 和 vendor→ grep -rlZ --exclude-dir={.git,node_modules,vendor} 'old_name' . | xargs -0 -r sed -i 's/old_name/new_name/g'

不排除这些目录,迟早会改到某个依赖的源码,diff 会长达 4 万行。

5. 只处理一种文件类型

clai
$ clai 只在 python 文件里替换→ grep -rlZ --include='*.py' 'old_name' . | xargs -0 -r sed -i 's/old_name/new_name/g'

--include 是 --exclude 的白名单版本。范围越窄越安全。

6. 保留备份——同时保持跨平台

clai
$ clai 做替换的同时给每个文件保留备份,在 mac 上也能用→ grep -rlZ 'old_name' proj/ | xargs -0 -r sed -i.bak 's/old_name/new_name/g'

sed -i.bak 会在每个被改动的文件旁边写一个 a.py.bak。这也是唯一一种在 GNU 和 BSD 的 sed 上都能用的写法:macOS 要求带后缀,Linux 两种都接受。

常见坑

  • 你的模式是正则表达式,不是普通字符串。 .、*、/ 和 $ 都是特殊字符。替换 config.yml 也会匹配到 configXyml;记得转义,或者换一个文本里不会出现的分隔符:sed -i 's|old/path|new/path|g'。
  • 先提交代码。 sed -i 没有撤销功能。替换前保持 git status 干净,出错时只需一句 git checkout .。
  • grep -l 每个文件只要匹配一次就停止——这正是它快的原因,也不影响替换本身,因为 sed 仍然会处理每一行。

相关问题

怎么先预览替换效果而不直接应用? 去掉 -i 让它直接打印:grep -rlZ 'old' . | xargs -0 sed 's/old/new/g' | less。或者用 grep -rn 'old' . 查看每一行及行号。

为什么什么都没发生? 通常是 xargs 拿到了空列表,或者你的模式没匹配到你以为的内容。单独跑一下 grep -rl 那部分看看。

能用 find 代替吗? 可以:find . -type f -name '*.py' -exec sed -i 's/old/new/g' {} +。但它会重写每个名字匹配的文件,不管里面有没有该模式,所以更推荐 grep -rl 的方案。

另请参阅

描述你想要的替换,看看 CliAI 生成的命令,再运行它。一行命令安装。