用sed编辑流时,最强大的命令莫过于它的替换命令。它有许多参数选项,可以完成诸多复杂的工作。
1. 替换命令的语法
sed '[address-range|pattern-range] s/original-string
/replacement-string/[substitute-flags]' inputfile
注意,上面的换行仅是为了阅读方便。下面我们逐项解释各个字段的含义:
- address-range 或pattern-range:可选参数,用于指定sed命令的作用范围,如果没指定,那么sed将作用于输入的所有行。
- s:替换命令
- original-string:在输入文本中查找的源字符串。它可以是正则表达式
- replacement-string:original-string将被替换为replacement-string
- substitute-flags:可选的控制参数选项
sed命令并不会改变源文件的内容,它的替换指令同样不会。它仅作用于模式空间中的内容,并把处理后的结果输出到stdout。下面我们看两个实例:
1.1 把所有‘Fu Jian’替换为‘Guang Dong’
sed 's/Fu Jian/Guang Dong/' data.txt
1.2 只把包含Zhao的行里的‘Zhe Jiang’替换为‘Fu Jian'
sed '/Zhao/s/Zhe Jiang/Fu Jian/' data.txt
2. 全局标识g
缺省sed命令只会替换每一行中查找到的第一个{original-string} 。如果你需要替换输入内容中的所有{original-string} ,那么你就需要全局标识g,它指示sed进行全局替换(当前行范围内)。
2.1 把第一个字母a替换为A
sed 's/a/A/' data.txt
2.2 替换所有字母a为A
sed 's/a/A/g' data.txt
3. 数值标识
可以用数值标识来指定第几次匹配到的字符串将被替换。数值的取值范围为1到512。比如说,/8标识将只替换第8次出现的字符串。
3.1 替换第2个a为A
sed 's/a/A/2' data.txt
4. 打印标识p
替换命令里的打印标识p就是控制打印行为的。在替换成功后,它打印出修改后的内容。和其它sed命令一样,与-n选项结合使用,改变sed的缺省行为。
4.1 把所有‘Fu Jian’替换为‘Guang Dong’,并只输出修改过的行
sed -n 's/Fu Jian/Guang Dong/p' data.txt
5. 写文件标识w
替换指令里的w标识作用于写文件。在替换成功后,它将修改后的内容写入一个文件。写脚本时,很多时候会用p来代替w,并通过重定向来写文件。
sed -n 's/Fu Jian/Guang Dong/w output.txt' data.txt
sed -n 's/Fu Jian/Guang Dong/p' data.txt > output.txt
6. 忽略大小写标识i
sed匹配是大小写敏感的。可以使用i标识转换为大小写不敏感的模式,但这仅是GNU sed提供的特性,并非通用版本参数。
7. 执行标识e
这也是GNU sed提供的特性。通过e标识,你可以在模式空间上执行任何可用的shell命令,它的执行结果会返回给模式空间。
这里假设存在一个文件files.txt,其内容:
/etc/passwd
/etc/group
执行:
sed 's/^/ls -l /' files.txt
它在文件的每一行的行首加上ls -l:
但是这样并不会真正执行shell命令。接下来,我们就用e标识让它执行起来:
sed 's/^/ls -l /e' files.txt
8. 组合sed替换标识
前面的示例中,都是使用单一的标识。但是,你可以根据需求,组合多个标识使用。比如:
sed -n 's/Li/Lee/gipw output.txt' data.txt
9. sed替换的分隔符
前面的示例中,一直使用缺省的sed分隔符斜杠‘/’。如果original-string或replacement-string里包含斜杠,那么,就需要对它进行转义。如果文本里描述的是路径,可能转义后会变成这样:
sed 's/\/usr\/local\/bin/\/usr\/bin/' path.txt
是不是很丑陋,书写又很麻烦?幸运的是,我们可以改变缺省的分隔符,很简单:
sed 's|/usr/local/bin|/usr/bin|' path.txt
sed 's^/usr/local/bin^/usr/bin^' path.txt
sed 's@/usr/local/bin@/usr/bin@' path.txt
sed 's!/usr/local/bin!/usr/bin!' path.txt
10. 作用同一行的多条替换命令
sed执行的流程是REPR,即,读取,执行,打印,重复。在执行阶段,可以有一条或多条sed命令,它们会依次执行。后面一条指令的输入,是前一条指令的输出,换句话说,如果前一条指令改变了模式空间里的某些内容,那么后一条指令处理的内容就不是原始输入的内容,而是前一条指令修改后的内容。
11. &-模式匹配
当replacement-string里使用&时,它既可以匹配常规的字符串,也可以执行模式匹配。给两个实例:
- 为ID加上方括号:
sed 's/^[0-9][0-9][0-9]/[&]/g' data.txt
- 为整行加上尖括号:
sed 's/^.*/<&>/' data.txt
12. 分组替换(单个分组)
sed可以使用分组,就像通常的正则表达式那样。分组以“\(”打头,并以“\)”结尾。分组可以和 back-referencing组合使用。Back-references是由分组选择的一个正则表达式的复用部分。sed里的Back-references可以出现在正则表达式里,也可以出现在替换命令中的replacement部分。
sed 's/\([^,]*\).*/\1/g' data.txt
命令说明:
- 正则表达式\([^,]*\)匹配字符合串到第一个逗号为止
- replacement-string里的\1替换第一个匹配的分组
- g是全局替换标识
总结起来就一句话,提取输出内容中的第一个字段。
试试下面这个:
sed 's/\([^:]*\).*/\1/' /etc/passwd
下面这个示例,为每个单词打头的字母加上一对括号()。
echo "The Geek Stuff" | sed 's/\(\b[A-Z]\)/\(\1\)/g'
13. 分组替换(多个分组)
sed支持多个分组替换,每个分组都由一个“\(”和一个“\)”来限定。当你在替换匹配的正则表达式中添加多个分组之后,可以在replacement串中通过 \n来指定第n个分组。
13.1 提取第一列和第三列字段
sed 's/\([^,]*\),\([^,]*\),\([^,]*\).*/\1,\3/g' data.txt
14. GNU版本的扩展标识
GNU版本的sed有些有趣的扩展标识,它们可用于替换命令中的 replacement-string部分。
- \l
- \L
- \u
- \U
- \E
它们的具体用途,请参考手册。