删除包含某个模式的行之前的n1行和之后的n2行。

8
sed -e '/XXXX/,+4d' fv.out

我需要在文件中找到一个特定的模式,并同时删除它上面的5行和下面的4行。我发现删除上面的一行会同时删除包含该模式的行和下面的四行。
sed -e '/XXXX/,~5d' fv.out

在sed手册中,它给出了一个波浪线(~),表示该行后面跟着的是模式。但是当我尝试时,被删除的是跟在模式后面的行。
那么,我该如何同时删除包含模式的行上面5行和下面4行呢?
5个回答

5

一种使用sed的方法,假设模式之间不太接近:

script.sed文件的内容:

## If line doesn't match the pattern...
/pattern/ ! { 

    ## Append line to 'hold space'.
    H   

    ## Copy content of 'hold space' to 'pattern space' to work with it.
    g   

    ## If there are more than 5 lines saved, print and remove the first
    ## one. It's like a FIFO.
    /\(\n[^\n]*\)\{6\}/ {

        ## Delete the first '\n' automatically added by previous 'H' command.
        s/^\n//
        ## Print until first '\n'.
        P   
        ## Delete data printed just before.
        s/[^\n]*//
        ## Save updated content to 'hold space'.
        h   
    } 

### Added to fix an error pointed out by potong in comments.
### =======================================================
    ## If last line, print lines left in 'hold space'.
    $ { 
        x   
        s/^\n//
        p   
    } 
### =======================================================


    ## Read next line.
    b   
}

## If line matches the pattern...
/pattern/ {

    ## Remove all content of 'hold space'. It has the five previous
    ## lines, which won't be printed.
    x   
    s/^.*$//
    x   

    ## Read next four lines and append them to 'pattern space'.
    N ; N ; N ; N 

    ## Delete all.
    s/^.*$//
}

运行方式:

sed -nf script.sed infile

谢谢,这正是我想要的(我已经将模式出现至少分隔20行)。 - Population Xplosive

2
使用awk的解决方案:
awk '$0 ~ "XXXX" { lines2del = 5; nlines = 0; }
     nlines == 5 { print lines[NR%5]; nlines-- }
     lines2del == 0 { lines[NR%5] = $0; nlines++ }
     lines2del > 0 { lines2del-- }
     END { while (nlines-- > 0)  { print lines[(NR - nlines) % 5] } }' fv.out

更新:

以下是该脚本的解释:

  • 使用旋转索引(NR%5;NR为记录号;在这种情况下为“lines”),我记住数组lines中的最后5行。
  • 如果在当前行中找到模式($0 ~ "XXXX$0为当前记录:在此情况下为一行;~扩展正则表达式匹配运算符),则重置读取的行数并记录有5行需要删除(包括当前行)。
  • 如果已经读取了5行,则打印当前行。
  • 如果没有要删除的行(如果已经读取了5行也是如此),则将当前行放入缓冲区并增加行数。请注意,如果打印了一行,则会递减然后递增行数。
  • 如果需要删除行,则不打印任何内容并递减要删除的行数。
  • 在脚本结束时,我打印出数组中的所有行。

我的原始脚本版本如下,但最终我对其进行了优化:

awk '$0 ~ "XXXX" { lines2del = 5; nlines = 0; }
     lines2del == 0 && nlines == 5 { print lines[NR%5]; lines[NR%5] }
     lines2del == 0 && nlines < 5 { lines[NR%5] = $0; nlines++ }
     lines2del > 0 { lines2del-- }
     END { while (nlines-- > 0)  { print lines[(NR - nlines) % 5] } }' fv.out

awk是一款非常好用的工具!我强烈建议你在互联网上找到一篇教程并阅读。其中一个重要的事情是:awk使用扩展正则表达式ERE)。它们的语法与sed中使用的标准正则表达式RE)略有不同,但所有可以用RE做的事情都可以用ERE完成。


谢谢。它正在工作。但是,我对awk完全是个新手。所以,你能解释一下这个脚本吗?另外,你如何分别修复n1和n2?在这里,似乎你已经取了n1=n2。 - Population Xplosive
我不理解你的n1/n2问题。脚本的解释非常简单,我正在更新帖子。 - jfg956
谢谢。我在想如何删除模式(n2)下面的4行。由于我没有awk的经验,所以我认为你需要将(n2)和(n1)之间的行数设置为相同。我非常困惑。如果我也学会了awk,那就太好了。 - Population Xplosive

1

这个想法是读取5行而不打印它们。如果你找到了模式,删除未打印的行和下面的4行。如果你没有找到模式,请记住当前行并打印第一行未打印的行。最后,打印未打印的内容。

sed -n -e '/XXXX/,+4{x;s/.*//;x;d}' -e '1,5H' -e '6,${H;g;s/\n//;P;s/[^\n]*//;h}' -e '${g;s/\n//;p;d}' fv.out

当然,这仅适用于文件中只有一个模式的情况。如果有多个模式,则需要在找到模式后读取5行新行,并且如果这些行中再次出现模式,则会变得复杂。在这种情况下,我认为sed不是正确的工具。


谢谢。它对第一次出现有效。但是,我有很多模式的出现。也许我需要将其放入循环中,以便grep不再显示该模式的任何出现。除了使用sed之外,您还有什么建议? - Population Xplosive
我正在处理下面的awk解决方案。在我看来,它更好,因为更易于理解和维护。 - jfg956

1

这个可能适合你:

sed 'H;$!d;g;s/\([^\n]*\n\)\{5\}[^\n]*PATTERN\([^\n]*\n\)\{5\}//g;s/.//' file

或者这样:

awk --posix -vORS='' -vRS='([^\n]*\n){5}[^\n]*PATTERN([^\n]*\n){5}' 1 file

一个更高效的sed解决方案:
sed ':a;/PATTERN/,+4d;/\([^\n]*\n\)\{5\}/{P;D};$q;N;ba' file

谢谢。它完美地运行了。但是,我有一些巨大的文件,它需要很长时间。 - Population Xplosive
@PopulationXplosive 我已经添加了一个 awk 解决方案。它可能会更快。 - potong
谢谢。awk的解决方案也需要很长时间。但是新的sed解决方案非常快。确实是一个漂亮的一行代码。 - Population Xplosive

1
如果你愿意将结果输出到文件而不是标准输出,vim 可以高效地完成这项任务:
vim -c 'g/pattern/-5,+4d' -c 'w! outfile|q!' infile

或者

vim -c 'g/pattern/-5,+4d' -c 'x' infile

在原地编辑文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接