1

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

如何使用 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 或任何其他脚本执行此操作!!!

4

6 回答 6

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.

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

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

    :g/hhhh/-6,+6s/^/#
    
于 2013-06-25T12:32:54.490 回答
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
于 2013-06-25T12:37:39.533 回答
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
于 2013-06-25T15:31:16.767 回答
0

** 这里有一个 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
于 2013-06-25T12:29:03.627 回答
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匹配项的范围内也只会添加一个。

于 2013-06-25T14:21:15.720 回答
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号:-B1grep -A1 -B1 -n hhhh file

7-gggg  
8:hhhh  
9-iii  

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

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   
#iiii  
jjjj  
呸呸呸  
llll  
嗯嗯  
呸呸呸  
哦哦  
于 2017-06-30T19:41:41.603 回答