原文参见:通过sed查找、替换文件中的文字
处理文本文件时,经常需要在一个或多个文件中查找并替换文字。
sed是“stream editor”(直译为流编辑器),可以对文件和输入流(例如管道)执行基本的文字操作。使用sed可以搜索、查找和替换、插入和删除单词和行。sed支持基本的及扩展的正则表达式,可以匹配复杂的模式。
本文将讨论如何使用sed查找和替换字符串,还将展示如何执行递归搜索和替换。
用sed来查找并替换字符串
有多个版本sed,这些版本功能上会有所差别。macOS使用BSD版本,而大多数Linux发行版均默认预装有GNU版本sed。本文介绍的是GNU版本。
使用sed搜索、替换文本的一般形式为:
$ sed -i 's/SEARCH_REGEX/REPLACEMENT/g' INPUTFILE
-i – 默认情况下,sed输出到标准输出。此选项告诉sed编辑指定的文件。如提供了扩展名(例如-i.bak),将创建原始文件的备份。
s – 替代命令,应为sed最常用的命令。
/ / / – 分隔符,可以是任何字符,通常使用斜杠(/)字符。
SEARCH_REGEX – 要搜索的字符串或正则表达式。
REPLACEMENT – 要替换成的字符串。
g – 全局替换标志。默认情况下,sed一行一行地读取文件,且仅更改行中第一次出现的SEARCH_REGEX。提供此替换标志后,所有找到的待替换字串都会被替换。
INPUTFILE – 运行sed命令的文件名。
好的命令习惯是在搜索替换参数上加引号,这样Shell的元字符不会按保留字符进行解析。
来看一个示例,具体看一下如何使用sed命令搜索、替换文件中一些常用的选项和标志。
出于演示目的,文件内容为:
file.txt
123 Foo foo foo foo /bin/bash Ubuntu foobar 456
如省略g标志,则仅替换每行中搜索字符串的第一个实例:
$ sed -i 's/foo/linux/' file.txt
文件内容修改为:
123 Foo linux foo linux /bin/bash Ubuntu foobar 456
使用全局替换标志g的搜索模式,则sed替换所有出现的搜索字串:
$ sed -i 's/foo/linux/g' file.txt
文件内容修改为:
123 Foo linux linux linux /bin/bash Ubuntu linuxbar 456
有可能会注意到,前面的示例中,字符串foo内的子字符串foobar也被替换了。如果不想替换子字符串,在搜索字符串的两端使用单词边界表达式(\b),这样将只进行全字匹配,确保部分单词不会被匹配上。
$ sed -i 's/\bfoo\b/linux/g' file.txt
运行命令后,文件修改为:
123 Foo linux linux linux /bin/bash Ubuntu foobar 456
如匹配过程不区分大小写,请使用I标志。下面的示例同时使用g和I标志:
123 Foo linux linux linux /bin/bash Ubuntu foobar 456
文件修改为:
123 linux linux linux linux /bin/bash Ubuntu linuxbar 456
如需查找、替换包含定界符(/)的字符串,就要使用反斜杠(\)来转义斜杠。例如替换/bin/bash为/usr/bin/zsh的命令是:
$ sed -i 's/\/bin\/bash/\/usr\/bin\/zsh/g' file.txt
更简易的作法是换用另一个定界符。大多数人使用竖线(|)或冒号(:),但是也可以使用任何其它字符:
$ sed -i 's|/bin/bash|/usr/bin/zsh|g' file.txt
文件会被修改为:
123 Foo foo foo foo /usr/bin/zsh Ubuntu foobar 456
也可以使用正则表达式。比如,搜索所有的3位数字,并替换为其它字符串(比如number):
$ sed -i 's/\b[0-9]\{3\}\b/number/g' file.txt
文件输出为:
number Foo foo foo foo /bin/bash demo foobar number
另一个sed挺有用的功能是,可以使用代表匹配内容的&字符。此字符可以多次使用。
例如,如果要在每个3位数字两边添加花括号{},请输入下面的命令:
$ sed -i 's/\b[0-9]\{3\}\b/{&}/g' file.txt
输出为:
{123} Foo foo foo foo /bin/bash demo foobar {456}
最后一点是,使用sed编辑文件之前先进行备份是一个好习惯。为此需提供-i选项。例如,要编辑file.txt并保存原文件为file.txt.bak,请使用以下命令:
$ sed -i.bak 's/foo/linux/g' file.txt
要确认是否备份文件已创建,可使用以下ls命令列出文件:
$ sed -i.bak 's/foo/linux/g' file.txt
递归查找、替换
有时想递归地在目录中搜索包含字符串的文件,并替换所有文件中的字符串,可以使用find或grep命令来递归地在目录中查找文件,并将文件名传递给sed来进行。
以下命令将在当前工作目录中递归搜索文件,并将文件名传递给sed。
$ find . -type f -exec sed -i 's/foo/bar/g' {} +
为避免名称中包含空格的文件出现,请使用-print0选项,该选项告诉find打印文件名并跟随一个空格,然后使用xargs -0通过输出管道传递给sed:
$ find . -type f -print0 | xargs -0 sed -i 's/foo/bar/g'
要排除目录,可使用-not -path选项。例如,如果要替换本地git存储库中的字符串,但排除所有以点(.)开头的文件,命令为:
$ find . -type f -print0 | xargs -0 sed -i 's/foo/bar/g'
如仅搜索、替换具有特定扩展名的文件里的文本,可以使用:
$ find . -type f -name "*.md" -print0 | xargs -0 sed -i 's/foo/bar/g'
另一个选择是使用grep命令以递归方式查找包含搜索模式的所有文件,然后将文件名传递给sed:
$ find . -type f -name "*.md" -print0 | xargs -0 sed -i 's/foo/bar/g'
最后
看起来可能有些复杂,但开始尝试后就会发现使用sed搜索、替换文件中的文本实际上非常简单。
要了解sed命令、选项和标志的更多信息,请访问GNU sed手册和Grymoire sed教程。如果有任何疑问或反馈,欢迎发表评论。