如何在sed中匹配后替换文本块

时间:2012-07-02 19:06:56

标签: linux sed

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

1)在更换后的块之后保持匹配线; 2)删除匹配的行以及那些行-3和+5。

请帮帮我。

谢谢。

3 个答案:

答案 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

说明:

命令分为两半:

  1. 前半部分保持3行的移动窗口。
  2. match之后追加5行。
  3. 详情如下:

    • :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