我如何评论(#)sed中匹配模式之前和之后的6行

时间:2013-06-25 12:14:22

标签: regex perl vim sed

我想在匹配的模式之前和之后评论(#)6行。 我提到了这个问题。

How do I delete a matching line, the line above and the one below it, using sed?

我尝试使用保持缓冲区来解决此问题,但无效。

我在文件中多次出现以下序列:

aaaa  
bbbb  
cccc  
dddd  
eeee  
ffff  
gggg  
hhhh  
iiii  
jjjj  
kkkk  
llll  
mmmm  
nnnn  
oooo  

如果我搜索hhhh,则输出文件应在下面给出:

  aaaa  
  #bbbb  
  #cccc  
  #dddd  
  #eeee  
  #ffff  
  #gggg  
  #hhhh  
  #iiii  
  #jjjj  
  #kkkk  
  #llll  
  #mmmm  
  #nnnn  
  oooo  

请帮我用sed或任何其他脚本来做这件事!!!

6 个答案:

答案 0 :(得分:12)

这个问题被标记为Vim,所以...我心爱的:help :global:help :normal来救援!

:g/hhhh/-6,+6norm I#

:substitute变体:

:g/hhhh/-6,+6s/^/#

故障:

  • :global命令用于为与给定模式匹配的每一行执行Ex命令。

    :g/hhhh/d会删除包含hhhh

  • 的每一行
  • Ex命令通常接受可选范围。范围可以使用绝对行号5,15和/或相对行号-3,+41

    :g/hhhh/-6,+6d会删除包含hhhh的每一行以上6行和6行之间的所有内容。

  • :normal命令允许我们从命令行执行常规命令,并接受范围,就像其他Ex命令一样。 I#是在行的开头插入#的最简单方法,因此我们可以从命令行执行:normal I#,这将我们带到第一个解决方案:

    :g/hhhh/-6,+6norm I#
    
  • 作为Ex命令,:substitute也接受范围,因此我们也可以使用它来在范围中的每一行的开头插入#,这将我们带到第二个解决方案:

    :g/hhhh/-6,+6s/^/#
    

答案 1 :(得分:4)

要在Perl中执行此操作,您需要将整个文件读入数组,然后找到匹配行的索引并编辑周围的行,这可以通过范围完成。

您必须从数组切片中删除未定义的值,否则如果您的匹配位于文件的开头或结尾附近(即少于6行),您将创建新条目。

perl -we '@a = <>;                      # read whole file
           for (0 .. $#a) {              # loop over indexes
               if ($a[$_] =~ /hhhh/) {   # find match
                   s/^/#/ for grep defined, @a[$_-6 .. $_+6]  # edit
               } 
           }; print @a" hhh.txt

在for循环$_中,别名为元素,这就是我们可以直接对其应用替换s///的原因。

使用Tie::File可以简化这一点。

<强>输出:

aaaa
#bbbb
#cccc
#dddd
#eeee
#ffff
#gggg
#hhhh
#iiii
#jjjj
#kkkk
#llll
#mmmm
#nnnn
oooo

答案 2 :(得分:2)

这可能适合你(GNU sed):

sed -r ':a;s/\n/&/6;tb;$!{N;ba};:b;/SEARCH_STRING/!{P;D};s/\n/&/12;tc;$!{N;bb};:c;s/^/#/gm' file

答案 3 :(得分:0)

**这里有一个Perl解决方案! **

我会将整个内容存储在一个Array中,遍历数组并在模式匹配时标记迭代器变量。然后从迭代器变量中删除并添加6,然后我们去,如果给出了标记变量,则可以在该行前面连接主题标签。

使其更清晰:

use File::Slurp;
my $find_counter = 0;
my $line_counter = 0;
my @lines = read_file( 'filename' ) ;
foreach my $line (@lines) { # foreach or for loop
  if ($line =~ /$pattern/) {
    $file_counter = $line_counter;
    last;
  }
  $line_counter++;
}
# loop again through @lines and when the line is between
# $file_counter + - 6 , concat the hashtag in front of the line

答案 4 :(得分:0)

另一种方法是一次读取一行,这对于大文件可能更好,它可以避免将整个文件读入内存。

数组@prev_lines包含匹配前要打印的行数。找到匹配项后,使用#前缀打印记住的行,并将$num_line_to_print设置为匹配后要打印的行数。如果该行不匹配,则查看是否要为上一个匹配打印行。如果两者都未将该行推入阵列,以防将来匹配。如果数组现在有太多行,它们不匹配匹配,所以只需打印它们。最后,在while循环之后只打印出任何保存行。

use strict;
use warnings;

my $num_lines_wanted = 6;

my @prev_lines;
my $num_line_to_print = 0;

while ( <> ) {
    if ( m/hhhh/ ) {
        while ( scalar(@prev_lines) > 0 ) {
            print "#", shift @prev_lines;
        }
        print "#", $_;
        $num_line_to_print = $num_lines_wanted;
    }
    elsif ( $num_line_to_print > 0 ) {
        print "#", $_;
        $num_line_to_print--;
    }
    else {
        push @prev_lines, $_;
        if ( scalar(@prev_lines) > $num_lines_wanted ) {
            print shift @prev_lines;
        }
    }
}

while ( scalar(@prev_lines) > 0 ) {
    print shift @prev_lines;
}

原始问题并不清楚如何处理输入,其中两条hhhh行在彼此的六行之内。这里的代码重新开始每次匹配的编号,它只打印输入行一次,即使一行在两个#匹配的范围内,也只添加一个hhhh

答案 5 :(得分:0)

对于那些对sed感到满意的人,我建议使用grep将上下文传送到sed以创建一些简单的sed命令:

<强> grep -A6 -B6 -n hhhh file | sed -e 's/[^0-9].*$//' -e 's/$/s|^|#|/' | sed -f- file

(以下示例包含-A1-B1以缩短此输出的长度。)

获取一行后的-A1行和-B1一行前面的匹配行grep -A1 -B1 -n hhhh file输出:

7-gggg  
8:hhhh  
9-iiii  

...我们将变成sed命令,用| sed -e 's/[^0-9].*$//' -e 's/$/s|^|#|/'注释掉那些带编号的行,两个sed命令删除第一个非数字后的所有内容,并用该替换该缩短行的结尾| sed -e 's|^|#|',替换整个行的注释。得到了这个:

7s|^|#|
8s|^|#|
9s|^|#|

...我们希望将这些命令传递给sed,因此我们使用等同于-f-的{​​{1}}并指示sed从stdin读取命令。

-f /dev/stdin

aaaa  
bbbb  
cccc  
dddd  
eeee  
ffff  
#gggg  
#hhhh  
#iiii  
jjjj  
kkkk  
llll  
mmmm  
nnnn  
oooo