中sed
,我想在匹配后替换多行文本块,例如匹配“foo”后,假设它的行号为0。我想将文本块从第-3行替换到第+5行,即匹配行之后的第三行和匹配行之后的第五行之间的文本块,由另一个文本块bar1\nbar2
。我希望能够在两种情况下做到这一点:
1) 保留被替换块后的匹配行;2) 将匹配行连同那些行-3 和+5 一起删除。
请帮我。
谢谢你。
多次使用 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”的自记录 sed 标签。它使我们能够跳转到其他代码——将其视为“goto”语句。由于是在开始,跳到那里会重复所有的 sed 命令。我们确实有一个目的 - N
,它读取下一行并将其附加到模式空间。这会一遍又一遍地重复,因此我们可以获得您想要检查(和删除)的那些上下文行并在它们上运行单个正则表达式。这是以下s
语句的工作,它首先查找\{3\}
前一个模式组 ( ) 的3 个重复 ( \([^\n]*\n\)
),这是任何类型的行。然后它会检查下一行是否有您要查找的标记字符串 (match
在这个例子中)和另外 5 行。如果此多行模式匹配,则进行替换并且工作几乎完成。我们需要使用循环,否则整个表达式将单独为每一行运行,一直提前读取而不是做我们想要的 - 批量读取这些行。
这可能有效(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
将下一行附加到 PSs/\n/&/3
自行替换第三个换行符。这是一个计数设备,用于检查 PS 中是否有 3 行。Ta
如果之前的替换失败循环到循环占位符a
/match/!{P;D}
看看match
,如果它失败打印到第一个换行符,然后删除该行,它是换行符(这会调用一个新循环)。:b
是循环占位符 注意此时已找到匹配项。$bc
如果文件结尾分支向前到占位符c
N
将下一行附加到 PSs/\n/&/8
自行替换第 8 个(后 5 个前 3 个)换行符。这是一个计数设备,用于检查 5 行是否附加到 PSTb
如果之前的替换失败循环到循环占位符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
一种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