2

sed,我想在匹配后替换多行文本块,例如匹配“foo”后,假设它的行号为0。我想将文本块从第-3行替换到第+5行,即匹配行之后的第三行和匹配行之后的第五行之间的文本块,由另一个文本块bar1\nbar2。我希望能够在两种情况下做到这一点:

1) 保留被替换块后的匹配行;2) 将匹配行连同那些行-3 和+5 一起删除。

请帮我。

谢谢你。

4

3 回答 3

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)。由于您可能要捕获多个块,请将 更改Tb,因此它将始终分支。如果它还没有自动发生。

根据要求提供更短的表格:

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 行。如果此多行模式匹配,则进行替换并且工作几乎完成。我们需要使用循环,否则整个表达式将单独为每一行运行,一直提前读取而不是做我们想要的 - 批量读取这些行。

于 2012-07-02T19:17:23.380 回答
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

解释:

命令分为两部分:

  1. 上半场保持3行的移动窗口。
  2. match后面附加了 5 行。

详细情况如下:

  • :a是循环占位符
  • $q在文件末尾打印模式空间 (PS) 内的所有行。
  • N将下一行附加到 PS
  • s/\n/&/3自行替换第三个换行符。这是一个计数设备,用于检查 PS 中是否有 3 行。
  • Ta如果之前的替换失败循环到循环占位符a
  • /match/!{P;D}看看match,如果它失败打印到第一个换行符,然后删除该行,它是换行符(这会调用一个新循环)。
  • :b是循环占位符 注意此时已找到匹配项。
  • $bc如果文件结尾分支向前到占位符c
  • N将下一行附加到 PS
  • s/\n/&/8自行替换第 8 个(后 5 个前 3 个)换行符。这是一个计数设备,用于检查 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
于 2012-07-03T16:05:48.760 回答
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
于 2012-07-03T13:50:23.987 回答