如何使用awk或sed进行递归查找和替换字符串?

915

我如何查找并替换每个出现的:

subdomainA.example.com
与...一起使用
subdomainB.example.com

如何递归地在 /home/www/ 目录树下的每个文本文件中进行操作?


115
提示:不要在 SVN 检出树中执行以下操作...它会覆盖特殊的 .svn 文件夹。 - J. Polfer
9
天啊,这正是我刚刚做的事情。但是它奏效了,并且似乎没有造成任何伤害。最糟糕的情况会是什么? - J. Katzwinkel
5
至少会破坏校验和,从而可能损坏您的代码库。 - ninjagecko
4
对于使用sed的所有人的快速提示:它会向你的文件中添加尾随换行符。如果您不想要它们,请先进行查找替换,但不要匹配任何内容,然后将其提交到git。然后再执行真正的操作。然后进行交互式变基并删除第一个操作。 - funroll
6
在将内容通过管道传递给xargs之前,可以使用-path ./.git -prune -ofind . -path ./.git -prune -o -type f -name '*matchThisText*' -print0中排除一个目录(例如git)的结果。 - devinbost
显示剩余3条评论
37个回答

988
find /home/www \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'
-print0参数告诉find命令用空字符(而不是新行)分隔每个结果的输出。即使您的目录中有文件名包含换行符,这种设置也可以让xargs命令在正确的文件名上工作。 \( -type d -name .git -prune \)是一个表达式,它完全跳过了所有名为.git的目录。如果您使用SVN或者有其他要保留的文件夹,只需匹配更多名称即可扩展它。 它与-not -path .git大致等效,但更有效率,因为它直接跳过整个目录而不是检查其中的每个文件。 在它之后需要使用-o参数,这是由于-prune实际上的工作方式所导致的。
有关更多信息,请参见man find

3
这对我很有效,我的情况是查找/替换IP地址值。但是问一下大家,为什么在第一个subdomainA\.example\.com值中点被转义了,而在第二个sudomainB.example.com值中未被转义?我按建议的格式执行了它,似乎完美地完成了工作,但我很好奇为什么只针对第一个字符串模式进行转义。 - elrobis
2
如果其中一个文件具有不可变标志,则此脚本将以错误“权限被拒绝”停止而未到达结尾。最好使用“-exec sed -i ... {} ;”而不是管道。 - Rafis Ganeev
我经常使用 find . -type f -print0 | xargs -0 sed -i -e 's/\r$//' 在特定目录中递归替换所有文件中的CRLF为LF。 - KaiserKatze
1
使用MACOS并感到沮丧为什么它不起作用 -> 尝试 -> find . \( ! -regex '.*/\..*' \) -type f | LC_ALL=C xargs sed -i '' 's/foo/bar/g' - Mushrankhan
2
@elrobis(12年后,但为了记录)第一个URL使用转义点,因为它在正则表达式匹配文本中并且是特殊的,但第二个URL在替换文本中,点在那种情况下不是特殊的。 - SensorSmith

506

对于我来说最简单的方法是

grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'

62
当你需要排除文件夹时,比如.svn文件夹,这种方法特别有效。例如:grep -rl oldtext . --exclude-dir=.svn | xargs sed -i 's/oldtext/newtext/g' - phyatt
51
在macOS上,sed -i命令会导致错误提示sed: 1: "file_path": invalid command code.。这是因为在macOS上-i标志的含义与其他平台不同。可以使用grep -rl old . | xargs sed -i "" -e 's/old/new/g' 命令进行替换操作。这篇文章提供了相关帮助。 - Ben Butterworth
5
如果你正在使用编译语言并想避免检查二进制文件,可以传递“-I”标志,例如:grep -Irl oldtext . | xargs sed -i 's/oldtext/newtext/g'。这个命令可以搜索指定目录下的所有文件,把包含"oldtext"的文本替换成"newtext"。 - TomDane
2
使用MACOS并感到沮丧为什么它不起作用 -> 尝试 -> grep -rl 'SEARCHSTRING' ./ | LC_ALL=C xargs sed -i'' 's/SEARCHSTRING/REPLACESTRING/g' - Mushrankhan
5
我发现你可以在grep命令后加上"-Z",在xargs命令后加上“-0”,以捕获带有空格的文件名:grep -rlZ oldtext . | xargs -0 sed -i 's/oldtext/newtext/g'。参考链接:https://dev59.com/83PYa4cB1Zd3GeqPmbeu - toto_tico
显示剩余8条评论

310

注意:不要在包含git仓库的文件夹上运行此命令,否则会破坏你的git索引。

find /home/www/ -type f -exec \
    sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

与其他回答相比,这个回答更简单,并且使用的是sed而不是perl,这正是原始问题所要求的。


57
请注意,如果您使用的是BSD版本的sed(包括Mac OS X),则需要在sed的-i选项中显式提供空字符串参数。例如: sed -i'' 's/original/replacement/g' - Nathan Craike
我该如何修改它以排除.git子文件夹? - reducing activity
@reducingactivity 你好!你可以使用以下命令:grep -rl placeholder . | grep -Ev ".git" | xargs sed -i s/placeholder/lol/g(grep -Ev 排除模式)。提示:在实际运行替换之前,先不要使用 -i 参数进行干运行。 - Eos Antigen

104

所有的技巧都差不多,但我喜欢这一个:

find <mydir> -type f -exec sed -i 's/<string1>/<string2>/g' {} +
  • find <mydir>:查找目录中的内容。

  • -type f

    文件类型为:普通文件

  • -exec command {} +

    此选项会在所选文件上运行指定命令,但命令行是通过将每个选择的文件名附加在末尾来构建的;该命令调用的总次数将远少于匹配的文件数。命令行的构建方式与xargs构建其命令行的方式非常相似。命令中只允许出现一个“{}”。命令在起始目录中执行。


54

对于我来说,最容易记住的解决方案是https://dev59.com/WXI95IYBdhLWcg3w3yFU#2113224,即:

sed -i '' -e 's/subdomainA/subdomainB/g' $(find /home/www/ -type f)

注意: -i '' 可解决OSX中的问题 sed: 1: "...": invalid command code .

注意: 如果要处理的文件过多,会出现 Argument list too long 的错误。解决方法是使用上面介绍的 find -exec 或者 xargs


2
在Cygwin上它会出现 sed: can't read : No such file or directory 的错误信息。为什么会出现这个问题,如何解决? - pmor

42
cd /home/www && find . -type f -print0 |
      xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'

1
需要进行一些解释,特别是因为它没有使用任何要求的工具(该问题也已标记为它们)。例如,什么是想法/主旨?请通过编辑您的答案来回复,而不是在评论中回复(不包括“编辑:”,“更新:”或类似内容 - 答案应该看起来像今天编写的)。 - Peter Mortensen

37

对于使用银色搜索器ag)的任何人

ag SearchString -l0 | xargs -0 sed -i 's/SearchString/Replacement/g'

由于ag默认忽略git/hg/svn文件/文件夹,因此在仓库内运行是安全的。


感谢提供可行的解决方案!我需要找到与 ripgrep 等效的替代方案。 - reducing activity
@reducingactivity 请查看 https://github.com/chmln/sd :) 我是一个快乐的用户。 - Jacob Wang
将ripgrep中的“ag”替换为“rg”也完全可以正常工作。 - mnme

24

最好使用git-grep-z选项和xargs -0一起使用。 - gniourf_gniourf
git grep 明显只有在 git 存储库中才有意义。一般替代方法是 grep -r - tripleee
@gniourf_gniourf 你能解释一下吗? - Petr Peller
3
使用-z参数,git-grep将使用空字节而非换行符来分隔输出字段;使用-0参数,xargs将使用空字节而非空格来读取输入,并避免处理引号等字符时出现问题。因此,如果您不想在文件名中包含空格、引号或其他特殊字符时破坏命令,则应该使用以下命令:git grep -z -l 'original_text' | xargs -0 sed ... - gniourf_gniourf

19

如果您需要排除目录--exclude-dir=..folder),并且可能还有带空格的文件名(使用grep -Zxargs -0中的0Byte来解决)的简单方法。

grep -rlZ oldtext . --exclude-dir=.folder | xargs -0 sed -i 's/oldtext/newtext/g'

所有我看到的7个以上的答案都忽略了空格! - cregox

19
为了减少需要递归 sed 处理的文件数量,您可以使用 grep 来查找字符串实例:

为了缩小递归搜索的范围,您可以使用 grep 命令来查找包含指定字符串的文件:

grep -rl <oldstring> /path/to/folder | xargs sed -i s^<oldstring>^<newstring>^g

如果你运行man grep,你会注意到你也可以定义一个--exlude-dir="*.git"标志,如果你想要省略搜索.git目录,避免像其他人礼貌地指出的那样遇到git索引问题。

带你到:

grep -rl --exclude-dir="*.git" <oldstring> /path/to/folder | xargs sed -i s^<oldstring>^<newstring>^g

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