如何用awk或sed递归查找/替换字符串?


674

我如何查找和替换每次出现的情况:

subdomainA.example.com

subdomainB.example.com

/home/www/目录树下的每个文本文件中递归?


93
提示:请勿在svn检出树中执行以下操作……它将覆盖魔术的.svn文件夹文件。
J. Polfer,2010年

7
哦,天哪,这正是我刚刚所做的。但是它起作用了,似乎没有造成任何伤害。可能发生的最坏情况是什么?
J. Katzwinkel

5
@ J.Katzwinkel:至少,它可能会破坏校验和,这可能会破坏您的存储库。
ninjagecko

3
使用sed的所有人的快速提示:它将在文件中添加尾随换行符。如果您不想要它们,请首先执行查找匹配项,该匹配项不匹配任何内容,然后将其提交给git。然后做真正的。然后以交互方式重新设置基准并删除第一个。
funroll

5
您可以通过-path ./.git -prune -ofind . -path ./.git -prune -o -type f -name '*matchThisText*' -print0管道传递到xargs之前使用in 来从结果中排除目录(例如git)
devinbost,2016年

Answers:


850
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


132
在OSX上,您可能会遇到sed: 1: "...": invalid command code .问题。看来-i选项需要扩展并解析's/../...'命令。解决方案:将扩展名''传递给-i选项,例如sed -i '' 's/...
罗伯特·卢霍

6
注意:如果您在目录上使用它,并且想知道为什么不svn st显示任何更改,那是因为您还修改了.svn目录中的文件!使用find . -maxdepth 1 -type f -print0 | xargs -0 sed -i 's/toreplace/replaced/g'代替。
ACK_stoverflow

57
另外,如果您在git repo中,请小心。我以为自己很聪明,可以在一个清晰的分支上对其进行测试,因此如果它做得不好,我可以还原,但会损坏我的git索引。
Ciryon

13
使用此选项grep -r 'hello' -l --null . | xargs -0 sed -i 's#hello#world#g'可避免编辑不相关的文件(sed可能会更改文件编码)。
caiguanhao

6
“但是反而破坏了我的git索引。” 不必对此太担心,您可以find .git ... | ... 'sed -i s/(the opposite from before)/g'修复git索引
Massey101 '16

259

注意:不要在包含git repo的文件夹上运行此命令-更改.git可能会损坏git索引。

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

与这里的其他答案相比,它比大多数答案更简单,并且使用sed代替了perl,这是原始问题的要求。


50
请注意,如果您使用的是BSD sed(包括在Mac OS X上),则需要为sed的-i选项提供一个明确的空字符串arg 。即: sed -i '' 's/original/replacement/g'
Nathan Craike 2012年

2
@JohnZwinck我的错,错过了+。不过,奇怪的是,尼基塔的解决方案对我来说运行得更快。
山姆

6
@AoeAoe:+大大减少了sed产生的进程数量。效率更高。
John Zwinck

4
如何在带有git repo的文件夹中安全地执行此操作?
Hatshepsut

20
如果您从查找结果中排除回购,则可以安全地在包含git回购的文件夹上执行find . -not -path '*/\.git*' -type f ...
戴尔·安德森

210

对我来说最简单的方法是

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

1
@Anatoly:仅一个问题:如何排除二进制文件(可执行文件)
user2284570

3
@ user2284570使用-I--binary-file=without-matchgrep标志。
Zéychin

34
当您需要排除目录(例如使用)时,这种方法特别有效.svn。例如:grep -rl oldtext . --exclude-dir=.svn | xargs sed -i 's/oldtext/newtext/g'
phyatt 2015年

11
brew install gnu-sedgsed在OSX上使用以避免造成痛苦。
P I

1
男人请注意,如果你的项目的Git版本控制,而不是使用这样的:git grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'。它不是很好,在所有他妈的你的.gitDIR
圣保罗

61

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

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

  • -type f

    文件类型:常规文件

  • -exec command {} +

    -exec操作的此变体在选定的文件上运行指定的命令,但是通过在末尾附加每个选定的文件名来构建命令行。该命令的调用总数将远远少于匹配文件的数目。命令行的构建与xargs构建命令行的方式几乎相同。命令中仅允许使用一个{}实例。该命令在起始目录中执行。


@ user2284570与-exec吗?尝试将路径设置为可执行文件而不是工具名称。
I159

@ I159:否:排除可执行二进制文件(但包括shell脚本)
user2284570 2014年

8
@ I159这个答案和约翰·兹温克的答案不一样吗?
恢复莫妮卡

1
@ user2284570“二进制文件”的概念并不完全清楚。您可以使用该file命令尝试确定每个文件的类型,但是其输出中的随机变化可能会有些令人困惑。该-I(又名--mime)选项可以帮助一些,或者--mime-type如果你有。遗憾的是,如何精确地重构此整洁的单代码来做到这一点,超出了这个小注释框的范围。如果您需要帮助,也许会发布一个单独的问题?(也许可以在此处添加带有链接的评论。)
Tripleee

1
最干净的答案!感谢队友
jukerok '19

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

2
我很好奇,是否有理由使用-print0xargs代替-execor -execdir
菲利普

4
从“ man find”中获得:对于每个匹配的文件,指定的命令运行一次。也就是说,如果/ home / www中有2000个文件,那么'find ... -exec ...'将导致对perl的2000次调用;而'找到... | xargs ...'仅会调用一次或两次perl(假设ARG_MAX约为32K,平均文件名长度为20)。
聘用俄罗斯人

2
@Employed Russian:这就是您要使用的原因find -exec command {} +-它确实避免了xargs之类的命令的过多调用,但是没有单独的过程。
John Zwinck

2
在哪个平台上?xargs解决方案是可移植的,“ find ... -exec”的“魔术”调用不会为找到的每个文件调用子进程,而不会。
俄罗斯

4
@EmployedRussian,find -exec ... {} +自2006
Charles Duffy

34

对我而言,最容易记住的解决方案是https://stackoverflow.com/a/2113224/565525,即:

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

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

注意:如果有太多文件需要处理Argument list too long。解决方法- 上述用途find -execxargs解决方案。


4
workaround应在所有情况下的首选语法。
恢复莫妮卡2014年

1
命令替换的问题$(find...)在于,shell无法处理带有空格或其他shell元字符的文件名。如果您知道这不是问题,则可以使用这种方法。但是我们有太多问题要问,人们没有对此问题发出警告或不理解警告。
三胞胎

30

对于使用Silver Searcherag)的任何人

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

由于ag默认会忽略git / hg / svn文件/文件夹,因此可以安全地在存储库中运行。


16

一个不错的oneliner作为额外。使用git grep。

git grep -lz 'subdomainA.example.com' | xargs -0 perl -i'' -pE "s/subdomainA.example.com/subdomainB.example.com/g"

3
如果在git repo内工作,这是个好主意,因为您不必冒险覆盖.git /内容(如对另一个答案的评论中所述)。
mahemoff 2014年

1
谢谢,我将它用作bash函数的refactor() { echo "Replacing $1 by $2 in all files in this git repository." git grep -lz $1| xargs -0 perl -i'' -pE "s/$1/$2/g" }用法,例如,将'word'替换为'sword':refactor word sword然后使用验证其作用git diff
Paul Rougieux

16

要减少要递归处理的文件sed,可以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

13

这与git仓库兼容,并且更简单:

Linux:

git grep -l 'original_text' | xargs sed -i 's/original_text/new_text/g'

苹果电脑:

git grep -l 'original_text' | xargs sed -i '' -e 's/original_text/new_text/g'

(感谢http://blog.jasonmeridth.com/posts/use-git-grep-to-replace-strings-in-files-in-your-git-repository/


明智地将git-grep-z选项与一起使用xargs -0
gniourf_gniourf

git grep显然只有在git回购中才有意义。一般替换为grep -r
三胞胎

@gniourf_gniourf你能解释吗?
Petr Peller

2
@PetrPeller:和一起-zgit-grep将输出字段用空字节而不是换行符分隔;和with一起-0xargs将读取由空字节分隔的输入,而不是空格(并且不要用引号引起奇怪的事情)。因此,如果您不希望命令在文件名包含空格,引号或其他有趣字符的情况下中断,则命令为:git grep -z -l 'original_text' | xargs -0 sed ...
gniourf_gniourf

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

find /home/www/ -type f 将列出/ home / www /(及其子目录)中的所有文件。“ -exec”标志告诉find在找到的每个文件上运行以下命令。

perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

是在文件上运行的命令(一次运行)。该{}被按文件名称进行替换。将+在命令的末尾告诉find给了很多文件名建立一个命令。

find手册页上:“命令行的构建与xargs构建命令行的方式几乎相同。”

因此,无需使用xargs -0或即可实现您的目标(并处理包含空格的文件名)-print0


8

我只需要这个,对可用示例的速度并不满意。所以我想出了自己的:

cd /var/www && ack-grep -l --print0 subdomainA.example.com | xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'

Ack-grep在查找相关文件方面非常有效。这条命令轻而易举地替换了约145 000个文件,而其他命令却花了很长时间,我等不及要等到它们完成。


不错,但是grep -ril 'subdomainA' *还不及grep -Hr 'subdomainA' * | cut -d: -f1
trusktr

@Henno:只有一个问题:如何排除二进制文件(可执行文件)
user2284570

ack-grep会自动为您执行此操作。
Henno 2014年

@Henno:它包括shell脚本吗?
user2284570

是。以下是其支持的文件类型的完整列表:beyondgrep.com/documentation
Henno 2014年

6

如果您需要排除目录--exclude-dir=.svn)并且文件名中带有空格(使用0Byte和grep -Zandxargs -0

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

6

最简单的替换方法(所有文件,目录,递归

find . -type f -not -path '*/\.*' -exec sed -i 's/foo/bar/g' {} +

注意:有时您可能需要忽略一些隐藏文件,即.git可以使用上述命令。

如果您想使用隐藏文件,

find . -type f  -exec sed -i 's/foo/bar/g' {} +

在这两种情况下,字符串foo都将被新字符串替换bar


5

grep -lr 'subdomainA.example.com' | while read file; do sed -i "s/subdomainA.example.com/subdomainB.example.com/g" "$file"; done

我想大多数人都不知道他们可以将某些内容传送到“ while读取文件”中,并且避免了那些讨厌的-print0 args,同时保留了文件名中的空格。

echo在sed之前进一步添加,可以让您在实际执行操作之前先查看将要更改的文件。


原因-print0是有用的,因为它while read可以处理根本无法处理的情况-换行符是Unix文件名中的有效字符,因此为了使代码完全健壮,它也需要处理此类文件名。(此外,您还要read -r避免在其中出现一些令人讨厌的POSIX旧式行为read。)
Tripleee

另外,sed如果没有匹配项,则该操作为空操作,因此这grep并不是必须的;尽管对于避免重写不包含任何匹配项的文件(如果有很多匹配项)或希望避免不必要地更新文件上的日期戳,这是一种有用的优化方法。
Tripleee '16

5

您可以使用awk如下解决此问题,

for file in `find /home/www -type f`
do
   awk '{gsub(/subdomainA.example.com/,"subdomainB.example.com"); print $0;}' $file > ./tempFile && mv ./tempFile $file;
done

希望这能够帮到你 !!!


可以在MacOs上解决任何问题!sed当甚至使用osx特定设置包括二进制文件时,所有基于命令的命令都会失败。
Jankapunkt

小心...如果find返回的任何文件的名称中都有空格,这会炸毁!使用起来更安全while readstackoverflow.com/a/9612560/1938956
Soren Bjornstad,

4

尝试这个:

sed -i 's/subdomainA/subdomainB/g' `grep -ril 'subdomainA' *`

1
@RikHic,您好,不错的提示-正在考虑这样的事情;不幸的是,上面的格式显示不正确:)因此,我将尝试使用pre标记(不起作用)-因此使用转义符转义:sed -i 's/subdomainA/subdomainB/g'` grep -ril 'subdomainA' /home/www/*`-这看起来仍然不太好,但是应该在复制粘贴中生存:)干杯!
sdaau 2011年

4
#!/usr/local/bin/bash -x

find * /home/www -type f | while read files
do

sedtest=$(sed -n '/^/,/$/p' "${files}" | sed -n '/subdomainA/p')

    if [ "${sedtest}" ]
    then
    sed s'/subdomainA/subdomainB/'g "${files}" > "${files}".tmp
    mv "${files}".tmp "${files}"
    fi

done

4

根据博客文章:

find . -type f | xargs perl -pi -e 's/oldtext/newtext/g;'

您如何逃脱斜线/?例如,我想更换IP地址:xxx.xxx.xxx.xxxxxx.xxx.xxx.xxx/folder
巴忒罗

您可以/使用\ 进行转义。例如:find . -type f | xargs perl -pi -e 's/xxx.xxx.xxx.xxx\/folder/newtext/g;'
J.Hpour

3

如果您不介意vimgrepfind工具一起使用,则可以跟踪用户Gert在此链接-> 如何在大文件夹层次结构中进行文本替换的答案

这是交易:

  • 递归grep表示要在特定路径中替换的字符串,并且仅采用匹配文件的完整路径。(那是$(grep 'string' 'pathname' -Rl)

  • (可选)如果您要在集中目录中对这些文件进行预备份,则可以使用以下方法: cp -iv $(grep 'string' 'pathname' -Rl) 'centralized-directory-pathname'

  • 之后,您可以vim按照与给定链接上提供的方案类似的方案随意编辑/替换:

    • :bufdo %s#string#replacement#gc | update

2

有点老派了,但这在OS X上也能用。

技巧不多:

•仅编辑.sls当前目录下具有扩展名的文件

.必须转义以确保sed不会将其评估为“任何字符”

,用作sed分隔符,而不是通常的/

还要注意,这是编辑Jinja模板以variable在的路径中传递a import(但这是不合主题的)。

首先,验证您的sed命令执行了您想要的操作(这只会将更改输出到stdout,而不会更改文件):

for file in $(find . -name *.sls -type f); do echo -e "\n$file: "; sed 's,foo\.bar,foo/bar/\"+baz+\"/,g' $file; done

准备好进行更改后,请根据需要编辑sed命令:

for file in $(find . -name *.sls -type f); do echo -e "\n$file: "; sed -i '' 's,foo\.bar,foo/bar/\"+baz+\"/,g' $file; done

请注意,-i ''sed命令中,我不想创建原始文件的备份(如OS X上sed的就地编辑中所述)在本页面或罗伯特·路约的评论)。

镇定快乐的人!


2

只是为了避免改变

  • NearlysubdomainA.example.com
  • subdomainA.example.comp.other

但仍然

  • subdomainA.example.com.IsIt.good

(在域根背后的想法可能不是很好)

find /home/www/ -type f -exec sed -i 's/\bsubdomainA\.example\.com\b/\1subdomainB.example.com\2/g' {} \;

2

我只用上衣:

find . -name '*.[c|cc|cp|cpp|m|mm|h]' -print0 |  xargs -0 tops -verbose  replace "verify_noerr(<b args>)" with "__Verify_noErr(<args>)" \
replace "check(<b args>)" with "__Check(<args>)" 

加一个''*。[c | cc | cp | cpp | m | mm | h]'`
FractalSpace

2

这个版本应该比大多数版本更通用。例如,它不需要finddu改为使用)。它确实需要xargs,仅在某些版本的Plan 9(如9front)中可以找到。

 du -a | awk -F' '  '{ print $2 }' | xargs sed -i -e 's/subdomainA\.example\.com/subdomainB.example.com/g'

如果要添加文件扩展名之类的过滤器,请使用grep

 du -a | grep "\.scala$" | awk -F' '  '{ print $2 }' | xargs sed -i -e 's/subdomainA\.example\.com/subdomainB.example.com/g'

1

对于IBMi上的Qshell(qsh),不执行OP标记的bash。

qsh命令的局限性:

  • find没有-print0选项
  • xargs没有-0选项
  • sed没有-i选项

因此在qsh中的解决方案:

    PATH='your/path/here'
    SEARCH=\'subdomainA.example.com\'
    REPLACE=\'subdomainB.example.com\'

    for file in $( find ${PATH} -P -type f ); do

            TEMP_FILE=${file}.${RANDOM}.temp_file

            if [ ! -e ${TEMP_FILE} ]; then
                    touch -C 819 ${TEMP_FILE}

                    sed -e 's/'$SEARCH'/'$REPLACE'/g' \
                    < ${file} > ${TEMP_FILE}

                    mv ${TEMP_FILE} ${file}
            fi
    done

注意事项:

  • 解决方案排除错误处理
  • 不是由OP标记的Bash

这有一些令人讨厌的引号问题,以及使用读取行for
三胞胎

1

如果您想在不完全破坏SVN存储库的情况下使用此功能,则可以通过执行以下操作来告诉'find'忽略所有隐藏文件:

find . \( ! -regex '.*/\..*' \) -type f -print0 | xargs -0 sed -i 's/subdomainA.example.com/subdomainB.example.com/g'

括号似乎是多余的。以前有一个格式错误,使其无法使用(Markdown渲染会占用正则表达式中的某些字符)。
3:59三胞胎

1

使用grep和的组合sed

for pp in $(grep -Rl looking_for_string)
do
    sed -i 's/looking_for_string/something_other/g' "${pp}"
done

@tripleee我对此做了一些修改。在这种情况下,命令所grep -Rl pattern生成的文件列表的输出在该模式所在的位置。文件不会for循环读取。
Pawel

??您仍然有一个for循环;如果任何返回的文件名包含空格,则它将无法正常工作,因为shell会标记化for参数列表。但是,然后在循环中使用不带引号的文件名变量,因此如果您对此进行修复,它将在此处中断。更正这些剩余的错误将使您的错误与@ MadMan2064的答案相同。
三人

@tripleee是的,是的,我错过了这一点。
Pawel

1

要替换git存储库中的所有匹配项,可以使用:

git ls-files -z | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'

请参阅在本地git repo中列出文件?其他选项以列出存储库中的所有文件。这些-z选项告诉git用零字节分隔文件名,从而确保xargs(使用选项-0)可以分隔文件名,即使它们包含空格或其他也不可以。


1
perl -p -i -e 's/oldthing/new_thingy/g' `grep -ril oldthing *`

1
不使用awk/ sed,但是perl很常见(仅嵌入式/具有busybox的系统除外)。
pevik

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.