解决方法主要来自网上搜到的一篇文章,但文中的大侠并没有解释得特别清楚,我对照着其他两个更晦涩的例子(一、二),结合man搞懂了之后,记录于此。
假设我们的目标文件test内容是这样的:
file content
aabbcc<<comment part 2>>>
ddeeff
现在需要把[[[...]]]这一段替换为“COMMENT”(为了说明的必要,没有用容易和正则相混淆的字符比如//*{}[]等来举例子),那么sed语法应当是:
:begin
/<<>>/ {
/>>>/! {
$! {
N;
b begin
}
}
s/<<<.*>>>/COMMENT/;
}
上述语句存储在test.sed中,那么执行的方式和结果就是:
$ sed -f test.sed test
file content
aabbccCOMMENT
ddeeff
把正则直接写到命令里面也可以,用“;”来分隔命令即可:
$ sed -e ":begin; /<<,/>>>/ { />>>/! { $! { N; b begin }; }; s/<<<.*>>>/COMMENT/; };" test
file content
aabbccCOMMENT
ddeeff
注意右花括号之后也要加上分号“;”,如果再加上-i参数就可以直接把改动写到原文件中去了。
怎么样?看懂了么?我来详细说明吧,看那个多行命令的test.sed文件的内容:
*
首先花括号{}代表命令块的开始,类似c的语法,后面就不再说了。
*
:begin,这是一个标号,man中叫做label,也就是跳转标记,供b和t命令用,本例中使用了b命令。
*
/<<,/>>>/,这是一个地址范围(Addresses),后面 {}中的命令只对地址范围之间的内容使用。其中逗号前面的部分是开始地址,逗号后面是结束地址,都是正则表达式。由于sed是“流”式“行”处理,所以结束地址是可以省略的,即如果地址的结束范围不存在,那么将一直处理到文件结尾。本例中使用这个地址范围主要是缩小处理的数据量,因为虽然后面用N命令把对一行的处理扩展为了多行,但如果从文件开头一直N扩展到<<<出现为止,buffer中要处理的字符串可能会很长,影响效率。所以去掉这个处理范围也是能够得到正确结果的,比如:
$ sed -e ":begin; { />>>/! { $! { N; b begin }; }; s/<<<.*>>>/COMMENT/; };" test
or
$ sed -e "{:begin; />>>/! { $! { N; b begin }; }; s/<<<.*>>>/COMMENT/; };" test
#
/>>>/!,>>>是要替换内容的结束标记,带上!就是说当一行处理完毕之后,如果没有发现结束标记。。。
#
$!,$在正则中表示字符串结尾,在sed中代表文件的最后一行,本句和上一句结合起来的意思就是:如果在本行没有发现结束标记,并且当前扫描过的行并不是文件的最后一行。
#
N;,把下一行的内容追加(append)到缓冲区(pattern)之后,在我们的例子中,在处理aabbcc<<
#
b begin,由于仍然没有找到结束标记<<<(注意上一条说的缓冲区还没有被处理),所以在这里跳回到标号begin,重新开始命令。如果开始和结束标记之间间隔了多行,那么就会有多次跳转发生。
#
s/<<<.*>>>/COMMENT/;,终于,/>>>/!不再匹配成功,也就是我们已经找到了结束标记,那么用s命令来进行替换。如果开始和结束标记在一行的话,就会越过上面那些复杂的处理,直接执行到这里了。
没有评论:
发表评论