命令行:在grep匹配的所有文件名中进行搜索和替换

81
我想要在所有与grep匹配的文件中搜索并替换一个字符串: grep -n 'foo' *会以以下形式给出输出:
[filename]:[line number]:[text]
对于grep返回的每个文件,我想通过将foo替换为bar来修改文件。
9个回答

108
这似乎是你想要的,基于你给出的例子:
sed -i 's/foo/bar/g' *

它不是递归的(不会进入子目录)。要在整个目录树中替换选定文件的好方法是使用find:

find . -name '*.html' -print -exec sed -i.bak 's/foo/bar/g' {} \;

*.html是文件名必须匹配的表达式,-i后面的.bak将原文件复制并重命名为.bak(你可以使用任何扩展名),g指示sed替换一行上的多个实例而不仅仅是第一个实例。-print用于方便显示匹配的文件。所有这些都取决于系统中这些工具的确切版本。


1
针对Cygwin用户的警告。find和sed组合似乎会更改通过流传输的文件的用户权限。这可以通过使用命令chmod -R 644 *从与find/sed操作时相同的目录级别来简单修复。 - kaskelotti
对于那些不想使用-i参数的人,我要提醒一句话:如果你不使用它,它是无法工作的(不要问我为什么)。 - knocte
4
@knocte 中的 -i 表示 sed 修改文件,否则 sed 只会将修改后的版本打印到标准输出。如果你不想创建 .bak 文件,只需省略 '.bak' 部分,-i 也可以单独使用。 - MattJ
2
在OSX上,您需要为find命令提供一个起始目录,例如find . -name '*.html'find directoryname/ -name '*' - Michiel Kauw-A-Tjoe
我需要添加一个“-e”,否则它会认为“s…”部分是后缀 sed -ie 's/foo/bar/g' * - vish
显示剩余2条评论

71

你的意思是在grep匹配到的所有文件中搜索并替换字符串吗?

perl -p -i -e 's/oldstring/newstring/g' `grep -ril searchpattern *`

编辑

由于这似乎是一个相当流行的问题,所以我想更新一下。

现在我主要使用ack-grep,因为它更加用户友好。因此,以上命令将变为:

perl -p -i -e 's/old/new/g' `ack -l searchpattern`

要处理文件名中的空格,你可以运行:

ack --print0 -l searchpattern | xargs -0 perl -p -i -e 's/old/new/g'

你可以用ack-grep做更多事情。比如说,你想要将搜索限制在仅HTML文件中:

ack --print0 --html -l searchpattern | xargs -0 perl -p -i -e 's/old/new/g'

如果不考虑空格的话,甚至可以更短:

perl -p -i -e 's/old/new/g' `ack -l --html searchpattern`
perl -p -i -e 's/old/new/g' `ack -f --html` # will match all html files

3
我认为这可能与包含空格的文件/文件夹有关。当尝试打开“未命名的文件夹/文件.txt”时,会出现“无法打开未命名:没有这样的文件或目录,<>第5行”的错误提示。 - Xeoncross

14
如果你的sed(1)有一个-i选项,那么可以像这样使用它:
for i in *; do
  sed -i 's/foo/bar/' $i
done

如果没有,可以根据你想使用的语言选择以下不同的变体方法:

ruby -i.bak -pe 'sub(%r{foo}, 'bar')' *
perl -pi.bak -e 's/foo/bar/' *

2
for i in *; do ... 是多余的,sed 可以接受文件列表作为参数。 - Jens
2
@Jens,为什么不在上面的例子中添加自己的示例来改进这个答案呢? - Magpie
变量应始终加引号 for i in *; do; sed -i 's/foo/bar/g' "$i"; done - cat

6

我喜欢并使用上述解决方案,或在数千个文件中进行系统范围的搜索和替换:

find -name '*.htm?' -print -exec sed -i.bak 's/foo/bar/g' {} \;

我认为使用“*.htm?”而不是.html可以同时搜索并找到.htm和.html文件。

我将.bak替换为更广泛使用的波浪号(~),以便更轻松地清理备份文件。


4

这可以使用grep而不需要使用perl或find来完成。

grep -rli 'old-word' * | xargs -i@ sed -i 's/old-word/new-word/g' @

我不知道“-i”在其他操作系统上不起作用。在我的Ubuntu上可以工作。 - pymarco
我的 xargs 手册(在 Ubuntu 上)说 -i 已经被弃用,应该使用 -I。因此,我们应该这样写:grep -rli 'old-word' * | xargs -I filepath sed -i 's/old-word/new-word/g' filepath - Max Wallace

3

find . -type f -print0 | xargs -0 <sed/perl/ruby cmd> 可以一次性处理包含多个空格的文件名,每批加载一个解释器,速度更快。


@knocte,“cmd”是代表任何给定工具的整个搜索和替换命令的标记。这个答案回答了如何处理包含空格的文件名的问题。 - Tony Adams

1
已经给出的答案是使用findsed的方法:find -name '*.html' -print -exec sed -i.bak 's/foo/bar/g' {} \;。这可能是标准答案。或者,您可以使用perl -pi -e s/foo/bar/g'代替sed命令。
对于大多数快速使用,您可能会发现命令rpl更容易记住。以下是在当前目录中递归地替换(foo -> bar)所有文件的示例:rpl -R foo bar .。它在大多数Linux发行版上默认不可用,但安装很快(apt-get install rpl或类似命令)。
然而,对于涉及正则表达式、回溯替换、文件重命名以及搜索和替换的更困难的任务,我知道的最通用和强大的工具是repren,这是我一段时间前为某些更棘手的重命名和重构任务编写的一个小Python脚本。您可能喜欢它的原因是:
  • 支持文件重命名以及对文件内容进行搜索和替换(包括在目录之间移动文件和创建新的父目录)。
  • 在执行搜索和替换之前查看更改。
  • 支持带有后向替换、整个单词、大小写不敏感和大小写保留(将 foo 替换为 bar,Foo 替换为 Bar,FOO 替换为 BAR)模式的正则表达式。
  • 可以处理多个替换,包括交换(foo -> bar 和 bar -> foo)或一组非唯一替换(foo -> bar,f -> x)。

查看 README 以获取示例。


1
这实际上比看起来的要容易。

不要解释。

grep -Rl 'foo' ./ | xargs -n 1 -I % sh -c "ls %; sed -i 's/foo/bar/g' %";
  • grep递归遍历您的树(-R),仅打印文件名(-l),从当前目录(./)开始
  • 这些内容通过管道传输给xargs,它以一个占位符(-I%)在shell命令(sh -c)中一次处理一个文件名
  • 在shell命令中,首先打印文件名(ls%;)
  • 然后sed在文件上执行内联操作(-i),使用foo / bar进行全局替换(s / foo / bar / g)(再次使用%表示文件)

易如反掌。如果您对find,grep,xargs,sed和awk有很好的掌握,那么在bash中进行文本文件操作几乎是不可能的 :)


算了,不用管了,pymarco已经在上面解释过了。我留下我的答案作为解释。 - siliconrockstar

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接