【问题标题】:how i can comment (#) 6 lines before and after the matched pattern in sed我如何在 sed 中匹配模式之前和之后评论 (#) 6 行
【发布时间】:2013-06-22 05:35:42
【问题描述】:

我想在匹配的模式前后注释(#) 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 或任何其他脚本来执行此操作!!!

【问题讨论】:

    标签: regex perl vim sed


    【解决方案1】:

    问题被标记为 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/^/#
      

    【讨论】:

    • 好的,请稍等。
    • 哇哦。我爱vim。我希望我可以两次投票给你。谢谢。
    • 这也可以像这样“一次性”完成(而不是:global):/hhhh/;-6,+6 s/^/#norm I# 也可以)。该模式设置光标位置(由于;,参见:help :;),那么从该点开始有效范围就是-6,+6
    • @ChrisJohnsen,非常有价值的评论,谢谢。我也是这样做的,但是 OP 说他的示例块在一个文件中出现了多次,所以我采用了更多 :global 的方法。
    【解决方案2】:

    要在 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
    

    【讨论】:

      【解决方案3】:

      这可能对你有用(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
      

      【讨论】:

      • 我已经扩展了一个班轮并添加了一些解释
      【解决方案4】:

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

      我会将整个内容存储在一个数组中,遍历数组并在模式匹配时标记迭代器变量。然后从迭代器变量中删除并添加 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
      

      【讨论】:

        【解决方案5】:

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

        数组@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 匹配的范围内。

        【讨论】:

        • @joshy 没问题。我只是想指出这个问题的一个未指明的方面,以及我的回答是如何处理它的。
        【解决方案6】:

        对于那些熟悉 sed 的人,我建议使用 grep 将上下文通过管道传送到 sed 以创建一些简单的 sed 命令:

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

        (下面的例子有-A1-B1来缩短这个输出的长度。)

        在与grep -A1 -B1 -n hhhh file 匹配的行之后获取行号-A1-B1 之前的一行,其输出:

        7-gggg 8:hhhh 9-iii

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

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

        ...我们希望将这些命令通过管道传输到 sed,因此我们使用 -f- 相当于 -f /dev/stdin 并指示 sed 从标准输入读取命令。

        grep -A1 -B1 -n hhhh abcd.txt | sed -e 's/[^0-9].*$//' -e 's/$/s|^|#|/' | sed -f- abcd.txt

        啊啊啊 bbbb cccc dddd eee ffff #gggg #hhhh #iii jjjj 呸呸呸 呸呸呸 嗯嗯 呸呸呸 哦哦

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-01-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-06-16
          • 1970-01-01
          • 2014-03-18
          • 2012-03-28
          相关资源
          最近更新 更多