在多个文件中进行查找/替换的最佳方法是什么?

51

最佳方法是什么?我不是命令行专家,但我认为可能可以使用 grepcat 的方法。

我只想替换在文件夹及其子文件夹中出现的字符串。如果有影响的话,我正在运行 ubuntu。


我只是在这里留下这个链接:https://dev59.com/IXVD5IYBdhLWcg3wE3No - Ehtesh Choudhury
7个回答

50

我来举个例子,对于使用 ag, The Silver Searcher 在多个文件上进行查找/替换操作的人来说,我还会再提供一个例子。

完整的示例:

ag -l "search string" | xargs sed -i '' -e 's/from/to/g'

如果我们将其分解,得到的是:
# returns a list of files containing matching string
ag -l "search string"

接下来,我们有:

# consume the list of piped files and prepare to run foregoing command
# for each file delimited by newline
xargs

最后,字符串替换命令:

# -i '' means edit files in place and the '' means do not create a backup
# -e 's/from/to/g' specifies the command to run, in this case,
# global, search and replace

sed -i '' -e 's/from/to/g'

5
对我来说这不再起作用了。我一直收到“sed无法读取:没有那个文件或目录”的错误提示。 - Lance
2
你应该执行 ag -l "text" | xargs -I FILE sed -i 's/from/to/g' FILE - Dmitry
1
这个人说:-i[SUFFIX],--in-place[=SUFFIX]:原地编辑文件(如果提供了SUFFIX,则进行备份)。这意味着应该这样写:ag -l "search string" | xargs sed -i -e 's/from/to/g',在 -i 后面不加值,否则我会得到与 @Lance 相同的错误。 - Bastien Adam

43
find . -type f -print0 | xargs -0 -n 1 sed -i -e 's/from/to/g'

这段代码的第一部分是一个查找命令,用于查找要更改的文件。可能需要适当修改。xargs 命令将查找到的每个文件并应用 sed 命令。sed 命令将每个 from 实例替换为 to 。这是一个标准的正则表达式,所以根据需要进行修改。

如果使用 svn,请注意。您的 .svn 目录也会被搜索和替换。您必须将其排除在外,例如:

find . ! -regex ".*[/]\.svn[/]?.*" -type f -print0 | xargs -0 -n 1 sed -i -e 's/from/to/g'
或者
find . -name .svn -prune -o -type f -print0 | xargs -0 -n 1 sed -i -e 's/from/to/g'

这个命令似乎没有递归进入子目录。我做错了什么吗? - damon
你为什么认为它没有递归进入目录? - Paul Tomblin
1
子目录中的文本文件没有进行更改。我想要它的操作是:在所有子目录中更改一些ip地址出现的地方为somewebsite.com。 - damon
如果更改后显示它找到了您的文件,则故障可能在于您的正则表达式。请在sed命令的“s / some.ip.address / somewebsite.com / g”部分中发布您正在使用的内容。 - Paul Tomblin
1
我使用的是Mac(Lion),我必须添加一个空字符串以防止应用后缀,即:-i'',否则它会将-e解释为后缀并重命名所有文件。完整示例:find . -type f -name '*.js' -print0 | xargs -0 -n 1 sed -i '' -e 's/from/to/g'(替换所有JavaScript文件中的内容)。 - Thorsten Lorenz
显示剩余5条评论

37

正如Paul所说,你首先需要找到想要编辑的文件,然后再进行编辑。使用GNU grep(Ubuntu默认使用)也是使用find的另一种选择,例如:

grep -r -l from . | xargs -0 -n 1 sed -i -e 's/from/to/g'

如果你只想要某种类型的文件,并且想要忽略版本控制目录中的内容,也可以使用ack-grep(sudo apt-get install ack-grep或访问http://petdance.com/ack/)。例如,如果你只想要文本文件,

ack -l --print0 --text from | xargs -0 -n 1 sed -i -e 's/from/to/g'
# `from` here is an arbitrary commonly occurring keyword

使用Perl是替代sed的一种选择,它可以每个命令处理多个文件,例如:

grep -r -l from . | xargs perl -pi.bak -e 's/from/to/g'

这里告诉Perl进行原地编辑,先创建.bak文件。

你可以根据个人偏好将管道的左侧和右侧组合在一起。


您可以使用“-prune”命令在“查找”中避免版本控制目录。例如,“find . -name RCS -prune -o -type f -print0”。 - Paul Tomblin
虽然使用类似 ack 的工具可以减少大脑负担,但这是真的。 - Emil Sit
ack还接受一个--print0选项,以使用null作为文件名之间的分隔符,因此您的ack命令将如下所示:ack -l --print0 --text from | xargs -0 -n 1 sed -i -e 's/from/to/g' - Josh Strater
谢谢,已经修正了行内文本。 - Emil Sit
1
注意,grep、ack、perl和sed并不都使用相同的正则表达式变体。由于Ack是用Perl编写的,因此如果将ack管道传输到perl中,则可以在两者中使用相同的“from”子句。 - Lloeki
第一种解决方案在OS X上不起作用......似乎grep返回的整个文件名列表被认为是单个文件名。 - Petr Peller

3

为了方便起见,我将Ulysse's answer(在纠正不良错误的打印后)转换为.zshrc / .bashrc函数:

function find-and-replace() {
  ag -l "$1" | xargs sed -i -e s/"$1"/"$2"/g
}

用法:查找和替换 Foo Bar

3

替代 sed 的方法是使用rpl(例如从http://rpl.sourceforge.net/或GNU/Linux发行版中获取),如rpl --recursive --verbose --whole-words 'F' 'A' grades/


你会如何在脚本中使用这个?我最终选择了:ag -l "$1" | xargs sed -i '' -e "s/$1/$2/g",但我担心在去掉单引号后参数扩展的问题。 - Connor

2

典型的(find|grep|ack|ag|rg)-xargs-sed组合存在一些问题:

  • 难以记住和正确使用。例如,忘记了xargs -r选项将在未找到文件时运行命令,可能会导致问题。
  • 检索文件列表和实际替换使用不同的CLI工具,并且可以具有不同的搜索行为。

这些问题足以针对递归搜索和替换这样的侵入性和危险操作开始开发专用工具:mo

早期测试似乎表明其性能介于agrg之间,并解决了我遇到的以下问题:

  • 单个调用可以过滤文件名和内容。以下命令在所有具有v1指示的源文件中搜索单词bug

    mo -f 'src/.*v1.*' -p bug -w

  • 一旦搜索结果正确,就可以添加实际替换bugfix

    mo -f 'src/.*v1.*' -p bug -w -r fix


我认为明确提到你是所推广工具的主要作者非常重要。 - kelvin

1
comment() { 
}
doc() { 
}
function agr { 
doc 'usage: from=sth to=another agr [ag-args]'
comment -l --files-with-matches

ag -0 -l "$from" "${@}" | pre-files "$from" "$to"
}
pre-files() {
doc 'stdin should be null-separated list of files that need replacement; $1 the string to replace, $2 the replacement.'
comment '-i backs up original input files with the supplied extension (leave empty for no backup; needed for in-place replacement.)(do not put whitespace between -i and its arg.)'
comment '-r, --no-run-if-empty
              If  the  standard input does not contain any nonblanks,
              do not run the command.  Normally, the command  is  run
              once  even  if there is no input.  This option is a GNU
              extension.'

AGR_FROM="$1" AGR_TO="$2" xargs -r0 perl -pi.pbak -e 's/$ENV{AGR_FROM}/$ENV{AGR_TO}/g'
}

您可以像这样使用它:

from=str1 to=sth agr path1 path2 ...

不指定路径将使用当前目录。 请注意,需要安装并添加到PATH中的工具有ag、xargs和perl。


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