在sed
中,我想在匹配后替换多行文本,例如,在匹配“foo”之后,假设其行号为0.我想替换文本块从第-3行到第+5行,即第三行在匹配行之前和匹配行之后的第五行之间的文本块,由另一个文本块bar1\nbar2
组成。我希望能够在两种情况下做到这一点:
1)在更换后的块之后保持匹配线; 2)删除匹配的行以及那些行-3和+5。
请帮帮我。
谢谢。
答案 0 :(得分:2)
多次使用N来读取八行,然后你可以将它们匹配,好像它们是连接的一样 - sed会在模式中重新识别\ n,因此很容易处理各个部分(行)。
示例:
$ echo '1
2 oooh
3
4
match
5
6
7
8
9 oooh
10 ' | sed ': label; N; s/[^\n]*\n[^\n]*\n[^\n]*\nmatch\n[^\n]*\n[^\n]*\n[^\n]*\n[^\n]*\n[^\n]*\n/bar1\nbar2/; T label'
它会一直读取直到它进行替换(T)。由于您可能需要捕获多个块,因此将T
更改为b
,因此它将始终分支。如果它没有自动发生。
要求的简短表格:
echo '1
2 oooh
3
4
match
5
6
7
8
9 oooh
10 ' | sed ': label; N; s/\([^\n]*\n\)\{3\}match\n\([^\n]*\n\)\{5\}/bar1\nbar2/; T label'
首先,我们定义一个名为“label”的selfdocumenting sed标签。它使我们能够跳转到其他代码 - 将其视为“goto”语句。由于它是在开始,跳转将重复所有sed命令。我们真的只有一个目的 - N
,它读取下一行并将其附加到模式空间。这是一遍又一遍地重复,因此我们可以获取您想要检查(和删除)的上下文行并对它们运行单个正则表达式。这是以下s
语句的作用,该语句首先查找前一个模式组(\{3\}
)的3次重复(\([^\n]*\n\)
),这是任何类型的行。然后它会检查您要查找的标记字符串的下一行(本例中为match
)和另外5行。如果此多行模式匹配,则进行替换并且作业几乎完成。我们需要使用循环,或者整个表达式将分别为每一行运行,一直向前读取而不是按照我们想要的方式运行 - 批量读取行。
答案 1 :(得分:2)
这可能有用(GNU sed):
seq 31|sed 's/5/& match/' >/tmp/file
sed ':a;$q;N;s/\n/&/3;Ta;/match/!{P;D};:b;$bc;N;s/\n/&/8;Tb;:c;s/.*/bar1\nbar2/' /tmp/file
1
bar1
bar2
11
bar1
bar2
21
bar1
bar2
31
sed ':a;$q;N;s/\n/&/3;Ta;/match/!{P;D};h;s/\([^\n]*\n\)*\([^\n]*match[^\n]*\).*/\2/;x;:b;$bc;N;s/\n/&/8;Tb;:c;s/.*/bar1\nbar2/;G' /tmp/file
1
bar1
bar2
5 match
11
bar1
bar2
15 match
21
bar1
bar2
25 match
31
说明:
命令分为两半:
match
之后追加5行。详情如下:
:a
是一个循环占位符$q
在文件结尾处打印模式空间(PS)中的所有行。N
将下一行添加到PS s/\n/&/3
自行替换第3个换行符。这是一个用于检查3条线路在PS中的计数设备。Ta
如果上一次替换失败,则循环到循环占位符a
/match/!{P;D}
查看match
,如果它失败则打印到第一个换行符然后删除该行并且它是换行符(这会调用一个新的循环)。:b
是一个循环占位符N.B.此时已找到匹配。$bc
如果文件结束分支转发到占位符c
N
将下一行添加到PS s/\n/&/8
替换第8个(之前5个之前的)换行符。这是一个用于检查5行是否附加到PS的计数装置Tb
如果上一次替换失败,则循环到循环占位符b
:c
是一个循环占位符s/.*/bar1\nbar2/
用必填字符串替换PS。第二个内衬制作match
行的副本并将其附加到替换字符串。
替代解决方案:
sed -r ':a;$!N;s/[^\n]*/&/9;$!Ta;/^([^\n]*\n){3}([^\n]*match[^\n]*)\n.*/!{P;D};c\bar1\nbar2' file
sed -r ':a;$!N;s/[^\n]+/&/9;$!Ta;/^([^\n]*\n){3}([^\n]*match[^\n]*)\n.*/!{P;D};s//\bar1\nbar2\n\2/' file
答案 2 :(得分:0)
使用GNU sed
进行第二种情况的一种方法,虽然看起来有点复杂(已经完全注释):
假设infile
有以下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
script.sed
的内容:
## From first line until a line that matches the pattern (number ten in
## this example), save lines in buffer and print each one when there are
## more than three lines between one of them and the line with the pattern
## to search.
0,/10/ {
## Mark 'a'
:a
## If line matches the pattern break this loop.
/10/ {
bb
}
## Until the pattern matches, if more than three lines (checking '\n') are
## saved, print the oldest one and delete it, because I only want to save last
## three.
/\(\n[^\n]*\)\{3\}/ {
P
D
}
## Append next line to pattern space and goto mark 'a' in a loop.
N
ba
}
## It should never match (I think), but it's a sanity check to avoid the
## following mark 'b'.
bc
## Here we are when found the line with the pattern, so read next five six
## lines and delete all of them but the sixth. If end of file found in this
## process none of them will be printed, so it seems ok.
:b
N;N;N;N;N
N
s/^.*\n//
## Here we are after deleting both '-3' and '+5' lines from the pattern matched,
## so only is left to print the remainder of the file in a loop.
:c
p
N
s/^.*\n//
bc
运行它,考虑到10
是第五行和第十一行代码中的模式。根据您的需要进行更改。在我的示例中,它应删除行7,8,9,10,11,12,13,14,15
:
sed -nf script.sed infile
使用以下输出:
1
2
3
4
5
6
16
17
18
19
20