如何从命令行进行递归查找和替换?


84

使用bash或zshell之类的shell,如何进行递归“查找并替换”?换句话说,我想在此目录及其子目录的所有文件中将所有出现的'foo'替换为'bar'。


对于同样的问题的备选答案可以在这里找到stackoverflow.com/questions/9704020/...
dunxd


在vim中尝试此操作可能是一个好主意。这样,您可以使用确认功能来确保不交换您不想要的内容。我不确定是否可以在整个目录范围内完成。
Samy Bencherif

Answers:


104

该命令将执行此操作(在Mac OS X Lion和Kubuntu Linux上都经过测试)。

# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

运作方式如下:

  1. find . -type f -name '*.txt'在当前目录(.)和以下目录中查找-type f名称以结尾的所有常规文件().txt
  2. | 将命令的输出(文件名列表)传递给下一个命令
  3. xargs 收集这些文件名并一一交给 sed
  4. sed -i '' -e 's/foo/bar/g'表示“在没有备份的情况下就地编辑文件,并且s/foo/bar每行(/g)多次进行以下替换()”(请参阅man sed

请注意,第4行中的“无需备份”部分对我来说还可以,因为无论如何我要更改的文件都受版本控制,因此如果出现错误,我可以轻松撤消。


9
没有该-print0选项,就永远无法通过管道找到到xargs的输出。您的命令将对名称中带有空格等的文件失败。
slhck

20
另外,只需find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +使用GNU find就能完成所有这些工作。
Daniel Andersson

6
sed: can't read : No such file or directory运行时得到提示find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g',但find . -name '*.md' -print0列出了许多文件。
Martin Thoma 2014年

9
这个工作对我来说,如果我删除之间的空间-i''
加拿大卢克

3
是什么意思''后,sed -i有什么''作用?
Jas 2015年

27
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +

这消除了xargs依赖性。


4
这不适用于GNU sed,因此在大多数系统上将失败。GNU sed要求您在-i和之间不留空格''
slhck

1
接受的答案可以更好地解释它。但是+1表示使用正确的语法。
orodbhen

9

如果您使用的是Git,则可以执行以下操作:

git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'

-l仅列出文件名。-z在每个结果之后输出一个空字节。

我之所以结束此操作,是因为项目中的某些文件在文件末尾没有换行符,即使没有其他更改,sed也添加了换行符。(没有评论文件末尾是否应该有换行符。)


1
此解决方案的大+1。其余的find ... -print0 | xargs -0 sed ...解决方案不仅需要花费更长的时间,而且还会向尚未添加换行符的文件中添加换行符,这对于在git repo中进行操作很麻烦。git grep相比之下,照明速度很快。
Josh Kupershmidt

3

这是我为此使用的zsh / perl函数:

change () {
        from=$1 
        shift
        to=$1 
        shift
        for file in $*
        do
                perl -i.bak -p -e "s{$from}{$to}g;" $file
                echo "Changing $from to $to in $file"
        done
}

我会用它执行

$ change foo bar **/*.java

(例如)


2

尝试:

sed -i 's/foo/bar/g' $(find . -type f)

在Ubuntu 12.04上测试。

如果子目录名称和/或文件名包含空格,则此命令将不起作用,但如果确实包含空格,请不要使用此命令,因为它将不起作用。

在目录名和文件名中使用空格通常是一个坏习惯。

http://linuxcommand.org/lc3_lts0020.php

查看“关于文件名的重要事实”


如果文件名称中带有空格,请尝试一下。(有一条经验法则说:“如果某件事看起来好得令人难以置信,那可能就是事实。”如果您“发现”了一个比其他任何人在3½年内发布的解决方案都更为紧凑的解决方案,那么您应该问自己为什么)
斯科特

1

使用此Shell脚本

现在,我使用此shell脚本,它将我从其他答案和搜索网络中学到的东西结合在一起。我将其放置在我的文件change夹中名为的文件中,$PATH并做了chmod +x change

#!/bin/bash
function err_echo {
  >&2 echo "$1"
}

function usage {
  err_echo "usage:"
  err_echo '  change old new foo.txt'
  err_echo '  change old new foo.txt *.html'
  err_echo '  change old new **\*.txt'
  exit 1
}

[ $# -eq 0 ] && err_echo "No args given" && usage

old_val=$1
shift
new_val=$1
shift
files=$* # the rest of the arguments

[ -z "$old_val" ]  && err_echo "No old value given" && usage
[ -z "$new_val" ]  && err_echo "No new value given" && usage
[ -z "$files" ]    && err_echo "No filenames given" && usage

for file in $files; do
  sed -i '' -e "s/$old_val/$new_val/g" $file
done

0

我的用例是我想替换为 foo:/Drive_Letterfoo:/bar/baz/xyz 以下代码可以实现。我在有大量文件的相同目录位置。

find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'

希望能有所帮助。


0

以下命令在Ubuntu和CentOS上运行良好;但是,在OS XI下不断出现错误:

find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;

sed:1:“ ./ Root”:无效的命令代码。

当我尝试通过xargs传递参数时,它工作正常,没有错误:

find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'

更改-i-i ''的事实可能比更改-exec为的事实更相关-print0 | xargs -0。顺便说一句,您可能不需要-e
斯科特

0
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

上面的工作就像一种魅力,但是对于链接目录,我必须为其添加-L标志。最终版本如下:

# Recursively find and replace in files
find -L . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.