如何在Linux命令行中替换多个文件中的字符串


460

我只需要ssh访问服务器,就需要替换文件夹中许多文件中的字符串。我怎样才能做到这一点?

Answers:


601
cd /path/to/your/folder
sed -i 's/foo/bar/g' *

出现的“ foo”将替换为“ bar”。

在macOS之类的BSD系统上,您需要-i '.bak'按联机帮助页提供一个备份扩展名,否则会出现“风险损坏或部分内容”。

cd /path/to/your/folder
sed -i '.bak' 's/foo/bar/g' *

13
如果字符串中包含空格或特殊字符,这对我似乎不起作用。知道为什么会这样,还是我需要如何逃避呢?谢谢!
马修·赫伯斯特

13
至少对于我的版本sed-i在其后需要一个字符串,该字符串将附加到旧文件名之后。因此,sed -i .bak 's/foo/bar/g' *.xx将所有.xx文件移动到等效.xx.bak名称,然后.xx使用foo→bar替换生成文件。
照应2014年

24
如果有人想寻找更多选择,可以在unix堆栈交换上找到答案,其中涉及更多用例站点unix.stackexchange.com/a/112024/13488
Reddy

19
@MatthewHerbst要转义空格,请使用\ like sed -i 's/foo\ with\ spaces/bar/g' *。我想您已经找到了这么长时间...但是这样,其他人发现相同的问题就可以了。
manuelvigarcia

5
对于递归操作,您可以尝试以下操作。请注意,如果文件列表很大,那将不起作用。 sed -i -e 's/foo/bar/g' $(find /home/user/base/dir)
苏格兰,

243

与Kaspar的答案类似,但带有g标志来替换一行中的所有匹配项。

find ./ -type f -exec sed -i 's/string1/string2/g' {} \;

对于不区分大小写的全局变量:

find ./ -type f -exec sed -i 's/string1/string2/gI' {} \;

23
如果您使用的是OSX,并且您的模式中可能包含点,并且您想就地替换(无备份文件),则应该使用LC_ALL=C find ./ -type f -exec sed -i '' -e 's/proc.priv/priv/g' {} \;(请参阅此帖子这篇文章
Jonathan H

2
这绝对对我有用,因为它可以使用-name“ * .js”'进行过滤
Marcello de Sales

1
一个详细的选项很酷,但是您可以重新grep看看是否进行了更改。注意:对于通配符,请尝试使用“ -name“ * .php””,而grep在使用递归和通配符时是不好的,您需要添加--include=*.whatever-r
PJ Brunet

12
不要从已签出的Git存储库的根目录执行此操作。您可能不小心在中损坏了其数据库.git/。确保cd降低到较低的水平。
erikprice

1
我刚刚在git repo中执行了此操作,现在git status返回:error: bad index file sha1 signature.... fatal: index file corrupt。是什么赋予了?

165

@kev的答案很好,但只影响即时目录中的文件。下面的示例使用grep递归查找文件。每次都对我有用。

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

命令细目

grep的-r--recursive递归地读每一目录下的所有文件。
grep -l-- print -with-matches,打印每个具有匹配项的文件的名称,而不是打印匹配的行。
grep -i:-- ignore-case

xargs:将STDIN转换为参数,请遵循以下答案
xargs的-i @〜命令中包含@〜 :到在一个特定的位置被用于参数的占位符〜命令〜,@符号是这可以通过任何字符串替换的占位符。

sed -i:就地编辑文件,不进行备份。
sed s / regexp / replacement /:用替换替换匹配regexp的字符串。 sed s / regexp / replacement / gglobal,对每个匹配项进行替换,而不是仅对第一个匹配项进行替换。


13
这对我没有用,但确实有:grep --include={*.php,*.html,*.js} -rnl './' -e "old-word" | xargs -i@ sed -i 's/old-word/new-word/g' @
丹尼斯

7
@pymarco您能解释一下此命令吗?我不知道为什么您必须使用xargs而不是仅使用sed after |,为什么还要-i在xargs命令中使用?我在手册中读到它已被弃用,应-I改为使用。并且@用作模式开始和结束的定界符吗?
Edson Horacio Junior

2
这个问题是专门针对linux的。
pymarco '16

6
在osx上没关系:grep -rli 'old-word' * | xargs -I@ sed -i '' 's/2.0.0/latest/g' @
Philippe Sultan's

1
如果您分解了某些选项(rli)和@符号,
那就

37

这为我工作:

find ./ -type f -exec sed -i 's/string1/string2/' {} \;

但是,这没有:sed -i 's/string1/string2/g' *。也许“ foo”不是字符串1,“ bar”不是string2。


1
这是因为sed对通配符*的处理不同。[abc] *表示集合{a,b,c}中的任意数量的字符。[a-z0-9] *的作用类似于通配符*。
thepiercingarrow '16

8
在OSX上使用:find ./ -type f -exec sed -i '' -e 's/string1/string2/' {} \;
Shaheen Ghiassy

32

已经列出了一些标准答案。通常,您可以使用find递归列出文件,然后使用sedperl进行操作。

对于大多数快速使用,您可能会发现命令rpl更容易记住。这是所有文件的递归替换(foo-> bar):

rpl -R foo bar .

您可能需要安装(apt-get install rpl或类似)。

但是,对于涉及正则表达式和向后替换,文件重命名以及搜索和替换的艰巨工作,我知道最通用,最强大的工具是repren,这是我为某些应用编写的一个小型Python脚本棘手的重命名和重构任务。您可能更喜欢的原因是:

  • 支持文件重命名以及文件内容的搜索和替换。
  • 在确认执行搜索和替换之前,请先查看更改。
  • 支持带有反向替换,整个单词,不区分大小写和保留大小写的正则表达式(替换foo-> bar,Foo-> Bar,FOO-> BAR)模式。
  • 适用于多种替换,包括交换(foo-> bar和bar-> foo)或一组非唯一替换(foo-> bar,f-> x)。

要使用它,pip install repren查看自述文件中的示例。


1
哇,repren很棒!刚用它在类名,方法和变量内更改单词的一部分,同时重命名文件以匹配1,000多个C ++头文件和源文件,并且第一次使用一个命令就可以完美地工作。谢谢!
Bob Kocisko '17

29

要替换多个文件中的字符串,可以使用:

grep -rl string1 somedir/ | xargs sed -i 's/string1/string2/g'

例如

grep -rl 'windows' ./ | xargs sed -i 's/windows/linux/g'

来源博客


20

要替换文件中的路径(避免使用转义符),可以使用以下命令:

sed -i 's@old_path@new_path@g'

@符号表示在后面的字符串中应忽略所有特殊字符。


2
正是我在所有其他答案中都在寻找的东西。即,如何处理特殊字符,例如在更改作为路径的字符串时。谢谢。在其他答案中看起来像是一个很大的疏忽。
启发

19

如果您的字符串中包含正斜杠(/),则可以将分隔符更改为“ +”。

find . -type f -exec sed -i 's+http://example.com+https://example.com+g' {} +

该命令将在当前目录中递归运行。


谢谢!帮助更改了../domain.comdomain.com
杰克罗杰斯

12

第一行出现的“ foo”将被替换为“ bar”。您可以使用第二行进行检查。

grep -rl 'foo' . | xargs sed -i 's/foo/bar/g'
grep 'foo' -r * | awk -F: {'print $1'} | sort -n | uniq -c

6
为什么要链接到博客?它包含与您的答案完全相同的文本。
DavidPostill '17

1
我喜欢grep -rl 'foo' . | xargs sed -i 's/foo/bar/g'
Prachi

10

如果您有文件列表,则可以使用

replace "old_string" "new_string" -- file_name1 file_name2 file_name3

如果您拥有所有文件,则可以使用

replace "old_string" "new_string" -- *

如果您具有带扩展名的文件列表,则可以使用

replace "old_string" "new_string" -- *.extension

2
实际上,至少在我的版本中,“
file

4
该实用程序分布在MySQL软件包中。
德米特里·金斯堡

2
尽管我想感谢解决方案,但该解决方案无需将正则行引用为正则表达式即可工作。
德米特里·金茨堡2014年

1
这可以正常工作-如果您想替换多个文件中的所有字符串(例如以“ .txt”结尾),您可以做replace "old_string" "new_string" -- *.txt
tsveti_iko

1
最好从何处获得此replace实用程序。没有这些信息,这个答案是不完整的。
Sitesh


7

假设您要搜索字符串search并将其替换为replace多个文件,这是我经过反复测试的单行公式

grep -RiIl 'search' | xargs sed -i 's/search/replace/g'

快速grep说明:

  • -R -递归搜索
  • -i - 不区分大小写
  • -I -跳过二进制文件(您想要文本,对不对?)
  • -l-打印一个简单列表作为输出。其他命令需要

然后将grep输出通过管道传递到sed(通过xargs),用于实际替换文本。该-i标志将直接更改文件。删除它以实现一种“空运行”模式。


4

在找到这个问题(和答案)之前,我确实设计了自己的解决方案。我搜索了“替换”,“几个”和“ xml”的不同组合,因为那是我的应用程序,但是没有找到这个特定的应用程序。

我的问题:我有带有测试用例数据的spring xml文件,其中包含复杂的对象。对Java源代码的重构更改了很多类,并且不适用于xml数据文件。为了保存测试用例数据,我需要更改分布在多个目录中的所有xml文件中的所有类名。一直保存原始xml文件的备份副本(尽管这不是必须的,因为版本控制会将我保存在这里)。

我一直在寻找find+的某种组合sed,因为它在其他情况下对我有用,但不能一次替换几个。

然后我发现Ask ubuntu响应,它帮助我构建了命令行:

find -name "*.xml" -exec sed -s --in-place=.bak -e 's/firstWord/newFirstWord/g;s/secondWord/newSecondWord/g;s/thirdWord/newThirdWord/g' {} \;

而且效果很好(嗯,我的案子有六个不同的替代品)。但是请注意,它将触摸当前目录下的所有* .xml文件。因此,如果您对版本控制系统负责,则可能需要先进行过滤,然后仅传递给sed实际拥有所需字符串的用户;喜欢:

find -name "*.xml" -exec grep -e "firstWord" -e "secondWord" -e "thirdWord" {} \; -exec sed -s --in-place=.bak -e 's/firstWord/newFirstWord/g;s/secondWord/newSecondWord/g;s/thirdWord/newThirdWord/g' {} \;

对于Windows,我只是发现有一种方法可以在一个命令中找到字符串-尚未检查如何替换它- findstr /spin /c:"quéquieresbuscar" *.xml 会很方便。
manuelvigarcia

3
grep --include={*.php,*.html} -rnl './' -e "old" | xargs -i@ sed -i 's/old/new/g' @

3

真是me脚,但我无法在OSX上正常使用任何sed命令,因此我做了以下愚蠢的事情:

:%s/foo/bar/g
:wn

^-将这三行复制到我的剪贴板中(是的,包括结尾的换行符),然后:

vi *

并按住command-v直到提示没有剩余文件。

愚蠢... hacky ...有效...



2

当用-i开关调用流编辑器时,它确实会“就地”修改多个文件,该文件以备份文件结尾作为参数。所以

sed -i.bak 's/foo/bar/g' *

在此文件夹中的所有文件中替换foobar,但不属于子文件夹。但是,这将为.bak目录中的每个文件生成一个新文件。要对该目录及其所有子目录中的所有文件进行递归操作,您需要一个诸如的帮助程序find来遍历目录树。

find ./ -print0 | xargs -0 sed -i.bak 's/foo/bar/g' *

find允许您通过指定其他参数(如)来进一步限制要修改的文件find ./ -name '*.php' -or -name '*.html' -print0


注意:GNU sed不需要文件结尾,sed -i 's/foo/bar/g' *也可以使用;FreeBSD sed需要扩展,但必须在两者之间留有空间,因此sed -i .bak s/foo/bar/g *可以使用。


2

用于multiedit命令的脚本

multiedit [-n PATTERN] OLDSTRING NEWSTRING

根据Kaspar的回答,我制作了一个bash脚本来接受命令行参数,并可以选择限制与模式匹配的文件名。保存在$ PATH中并使其可执行,然后使用上面的命令。

这是脚本:

#!/bin/bash
_help="\n
Replace OLDSTRING with NEWSTRING recursively starting from current directory\n
multiedit [-n PATTERN] OLDSTRING NEWSTRING\n

[-n PATTERN] option limits to filenames matching PATTERN\n
Note: backslash escape special characters\n
Note: enclose STRINGS with spaces in double quotes\n
Example to limit the edit to python files:\n
multiedit -n \*.py \"OLD STRING\" NEWSTRING\n"

# ensure correct number of arguments, otherwise display help...
if [ $# -lt 2 ] || [ $# -gt 4 ]; then echo -e $_help ; exit ; fi
if [ $1 == "-n" ]; then  # if -n option is given:
        # replace OLDSTRING with NEWSTRING recursively in files matching PATTERN
        find ./ -type f -name "$2" -exec sed -i "s/$3/$4/g" {} \;
else
        # replace OLDSTRING with NEWSTRING recursively in all files
        find ./ -type f -exec sed -i "s/$1/$2/" {} \;
fi

1

如果文件包含反斜杠(通常是路径),则可以尝试如下操作:

sed -i -- 's,<path1>,<path2>,g' *

例如:

sed -i -- 's,/foo/bar,/new/foo/bar,g' *.sh (in all shell scripts available)

1

为了维护我的个人英语节点,我编写了一个实用程序脚本,该脚本可以递归替换目录中所有文件的多对旧/新字符串。

在哈希映射中管理多对旧/新字符串。

可以通过命令行或环境变量来设置目录,该映射在脚本中进行了硬编码,但是您可以根据需要修改代码以从文件中加载。

由于某些新功能,它需要bash 4.2。

en_standardize.sh:

#! /bin/bash
# (need bash 4.2+,)
# 
# Standardize phonetic symbol of English.
# 
# format:
#   en_standardize.sh [<dir>]
# 
# params:
# * dir
#   target dir, optional,
#   if not specified then use environment variable "$node_dir_en",
#   if both not provided, then will not execute,
# * 
# 

paramCount=$#

# figure target dir,
if [ $paramCount -ge 1 ]; then # dir specified
    echo -e "dir specified (in command):\n\t$1\n"
    targetDir=$1
elif [[ -v node_dir_en ]]; then # environable set,
    echo -e "dir specified (in environment vairable):\n\t$node_dir_en\n"
    targetDir=$node_dir_en
else # environable not set,
    echo "dir not specified, won't execute"
    exit
fi

# check whether dir exists,
if [ -d $targetDir ]; then
    cd $targetDir
else
    echo -e "invalid dir location:\n\t$targetDir\n"
    exit
fi

# initial map,
declare -A itemMap
itemMap=( ["ɪ"]="i" ["ː"]=":" ["ɜ"]="ə" ["ɒ"]="ɔ" ["ʊ"]="u" ["ɛ"]="e")

# print item maps,
echo 'maps:'
for key in "${!itemMap[@]}"; do
    echo -e "\t$key\t->\t${itemMap[$key]}"
done
echo -e '\n'

# do replace,
for key in "${!itemMap[@]}"; do
    grep -rli "$key" * | xargs -i@ sed -i "s/$key/${itemMap[$key]}/g" @
done

echo -e "\nDone."
exit

1

这样使用ack命令会更快:

ack '25 Essex' -l | xargs sed -i 's/The\ fox \jump/abc 321/g'

另外,如果搜索结果中有空格。您需要逃脱它。


0

我正在举一个示例,修复python源中常见的shebang错误。

您可以尝试使用grep / sed方法。这是一个与GNU sed兼容并且不会破坏git repo的代码:

$ grep -rli --exclude '*.git*' '#!/usr/bin/python' . | xargs -I {} \
gsed -i '' -e 's/#!\/usr\/bin\/python/#!\/usr\/bin\/env python/' {}

或者你可以使用greptile :)

$ greptile -x .py -l -i -g '#!/usr/bin/env python' -r '#!/usr/bin/python' .

我刚刚测试了第一个脚本,第二个脚本也应该工作。小心转义字符,我认为在大多数情况下使用爬行动物应该更容易。当然,您可以使用sed做许多有趣的事情,因此最好与xargs一起使用它。


0

我从另一篇文章中找到了这篇文章(不记得是哪篇文章),虽然它不是最优雅的,但它很简单,并且作为Linux新手,我没有遇到任何麻烦

for i in *old_str* ; do mv -v "$i" "${i/\old_str/new_str}" ; done

如果您有空格或其他特殊字符,请使用\

for i in *old_str\ * ; do mv -v "$i" "${i/\old_str\ /new_str}" ; done

对于子目录中的字符串,请使用**

for i in *\*old_str\ * ; do mv -v "$i" "${i/\old_str\ /new_str}" ; done

0

以下命令可用于首先搜索文件并替换文件:

find . | xargs grep 'search string' | sed 's/search string/new string/g'

例如

find . | xargs grep abc | sed 's/abc/xyz/g'

0

使用简单的脚本文件有一种更简单的方法:

   # sudo chmod +x /bin/replace_string_files_present_dir

在gedit或您选择的编辑器中打开文件,我在这里使用gedit。

   # sudo gedit /bin/replace_string_files_present_dir

然后在编辑器中将以下内容粘贴到文件中

   #!/bin/bash
   replace "oldstring" "newstring" -- *
   replace "oldstring1" "newstring2" -- *
   #add as many lines of replace as there are your strings to be replaced for 
   #example here i have two sets of strings to replace which are oldstring and 
   #oldstring1 so I use two replace lines.

保存文件,关闭gedit,然后退出终端或关闭它,然后启动它以加载您添加的新脚本。

导航到您要编辑多个文件的目录。然后运行:

  #replace_string_files_present_dir

按回车键,这将自动替换oldstringoldstring1在所有包含他们用正确的文件newstringnewstring1分别。

它将跳过所有不包含旧字符串的目录和文件。

如果您有多个目录包含需要替换字符串的文件,这可能有助于消除繁琐的键入工作。您所需要做的就是导航到每个目录,然后运行:

#replace_string_files_present_dir

您所要做的就是确保您已包含或添加了所有替换字符串,如我上面显示的那样:

replace "oldstring" "newstring" -- *

在文件/ bin / replace_string_files_present_dir的末尾。

要添加新的替换字符串,只需在终端中键入以下内容来打开我们创建的脚本:

sudo gedit /bin/replace_string_files_present_dir

不必担心您添加的替换字符串的数量,如果找不到旧字符串,它们将无效。


通常,当人们用bash询问“如何$ {whatever}”时,他们会要求提供一个精简版本以包含在脚本或CI作业指令中(例如a .gitlab-ci.yml或a travis.yml)。编写脚本并在以后执行该脚本的
每一步

-4

$ replace“ From”“到”-`find / path / to / folder -type f`

(替换-MySQL数据库系统的字符串替换实用程序)

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.