删除给定匹配行周围的行

zzx*_*xyz 2 perl

我有一个看起来像这样的日志文件:

2018/10/08 17:11:28 [debug] 8851#0: *2 Sent 8/8 bytes.
2018/10/08 17:11:28 [debug] 8851#0: *2 Session: Staging 8 bytes in thread buffer.
2018/10/08 17:11:33 [debug] 8851#0: *36 Receiving 8 bytes
2018/10/08 17:11:33 [debug] 8851#0: *36 Session: Staging 8 bytes in thread buffer.
2018/10/08 17:11:33 [debug] 8851#0: *36 Handling TRL request #0001: [GET_REGION_INFO].
2018/10/08 17:11:33 [debug] 8851#0: *36 Sent 8/8 bytes.
2018/10/08 17:11:33 [debug] 8851#0: *36 Finished processing TRL request #0001.
2018/10/08 17:11:33 [debug] 8851#0: *36 GET_REGION_INFO: Staging 99 bytes in thread buffer.
2018/10/08 17:11:33 [debug] 8851#0: *36 Sent 99/99 bytes.
2018/10/08 17:11:33 [debug] 8851#0: *36 Session: Staging 8 bytes in thread buffer.
2018/10/08 17:11:33 [debug] 8851#0: *36 Sent 8/8 bytes.
2018/10/08 17:11:38 [debug] 8851#0: *22 Receiving 8 bytes
2018/10/08 17:11:38 [debug] 8851#0: *22 Session: Staging 8 bytes in thread buffer.
Run Code Online (Sandbox Code Playgroud)

基于包含的行[GET_REGION_INFO],我想删除它附近的所有行,具有相同的请求ID(*36在这种情况下)..在任一方向的10行内.

到目前为止我得到的是......它可以从扫描线开始工作......问题是我甚至认为这种基本方法根本无法在其上方获得匹配线.(他们已经打印过一件事)

perl -lane '$requestId=$F[4] if /\[GET_REGION_INFO\]/;$requestId="Z" if $requestId ne $F[4]; print if $requestId eq "Z";' error.log
Run Code Online (Sandbox Code Playgroud)

我能想到的唯一方法似乎容易出错,而且过于复杂.此文件是关于技嘉,让啜整个事情是我想希望避免......虽然该机拥有32GB,所以....

任何人都可以建议一个相当简单的方法吗?对于一个真正的perl脚本而不是单行程,我很好.

我应该注意,它*36是一个请求id(如变量所示),理论上可以混合多个请求.但这很少见,所以如果脚本无法删除非连续行(例如我当前的脚本),则可以.

哦,最后一件事.请求ID最终被回收,所以我不能做任何聪明的事情,比如建立一个列表,然后用该列表解析整个文件.

根据样本输入,预期输出:

2018/10/08 17:11:28 [debug] 8851#0: *2 Sent 8/8 bytes.
2018/10/08 17:11:28 [debug] 8851#0: *2 Session: Staging 8 bytes in thread buffer.
2018/10/08 17:11:38 [debug] 8851#0: *22 Receiving 8 bytes
2018/10/08 17:11:38 [debug] 8851#0: *22 Session: Staging 8 bytes in thread buffer.
Run Code Online (Sandbox Code Playgroud)

zdi*_*dim 6

问题:在某些具有特定请求ID的行上会发生某个短语.应该跳过与具有短语的那个之间的给定距离内的该请求id的任何行,以及该行本身.

我们不知道发生了哪个请求ID,也可能通过文件发生了什么变化.

首先阅读,直到短语显示,将行保存在缓冲区中.一旦我们解析了该行的request-id,我们知道它是哪一个.然后处理累积的缓冲区,打印其他请求ID的所有行,并且仅打印"选择"的行的跳过距离以外的行.

然后继续将行收集到缓冲区中,直到再次找到该短语.处理缓冲区:打印其他id的所有行,并且只打印与所选id的两个短语相距足够的行.

下面的代码使用发布数据的变体进行测试.它解释了可能混合具有不同请求ID的行; 删除它,假设任何request-id的行总是在一个块中,将大大简化代码,并加快它.

use warnings;
use strict;
use feature 'say';

my ($file, $skip_dist) = @ARGV;
die "Usage: $0 log-file [skip-distance]\n" if not $file;    
$skip_dist //= 2;  #/

my $trigger = qr{\[GET_REGION_INFO\]};

open my $fh, '<', $file  or die "Can't open $file: $!";

my (@buf, $req_mark, $skip_idx, $next_req_cnt);

while (<$fh>) {    
    if (not $req_mark and /$trigger/) {
        # Find the req_id of interest and save it into req_mark,
        # then process the accumulated buffer
        my ($req_id, $msg) = /:\s+(\*[0-9]+)\s+(.*)/;
        $req_mark = $req_id;

        # Find position of req_id which is skip_dist before the mark
        # and print lines for req_mark before it (and all others)
        my $del_idx = find_skip_start($req_mark, \@buf, $skip_dist);
        for my $i (0..$#buf) {
            if ($skip_idx and $i < $skip_idx) { print $buf[$i] }
            else {
                my ($req_id) = $buf[$i] =~ /:\s+(\*[0-9]+)/;
                print $buf[$i] if $req_id ne $req_mark;
            }
        }
        @buf = (); 
        $skip_idx = 0;
    }
    elsif (/$trigger/ or eof) { 
        # Process buffer collected between previous and this trigger,
        # Or up to the end of file (the last line then need be added)
        push @buf, $_  if eof;
        my $skip_idx = (not eof) 
            ? find_skip_start($req_mark, \@buf, $skip_dist) 
            : $#buf+1;
        for my $i (0..$#buf) { 
            my ($req_id) = $buf[$i] =~ /:\s+(\*[0-9]+)/;
            print $buf[$i]
                if  $req_id ne $req_mark
                or (++$next_req_cnt > $skip_dist and $i < $skip_idx);
        }
        @buf = (); 
        $next_req_cnt = $skip_idx = 0;

        # Check whether the request-id changed and update for next buffer
        my ($req_id) = /:\s+(\*[0-9]+)/;
        if ($req_id ne $req_mark) {
            $req_mark = $req_id 
        }

    }   
    else { push @buf, $_ }
}

sub find_skip_start {
    my ($req_mark, $buf, $skip_dist) = @_;
    my ($skip_idx, $prev_req_cnt);
    for my $i (0..$#$buf) {
        my ($req_id) = $buf->[$#$buf-$i] =~ /:\s+(\*[0-9]+)/;
        if ( $req_id eq $req_mark    and
            (++$prev_req_cnt >= $skip_dist) )
        {
            $skip_idx = $#$buf-$i;
            last;
        }
    }
    return $skip_idx;
}
Run Code Online (Sandbox Code Playgroud)

有许多地方可以提高效率.

一个主要的效率问题涉及缓冲区大小的核心问题.在我们触发之前可以收集多少数据?如果[GET_..]文件中只有几行,我们最终会积累数十亿字节的数据怎么办?然后最好不时地减轻(打印一些)缓冲区; 但是如果从来没有太多的数据那么部分缓冲区的清空会使问题变得复杂,因为这个短语可能会提前关闭(还要保留多少行?).

如果不知道该短语的频率以及请求ID的频率,则无法回答这个问题.然后,一个优化是首先查看文件并估计这些频率,然后对此做出决定.但是,这不太可靠,因为无法保证日志文件在任何意义上都是一致的.

上面的代码清楚地假设永远不会有太多的数据,因此我们不会造成麻烦,但如果它变得太大,添加一个检查并写出缓冲区的一部分是不错的.


为了记录,当我在OP样本数据上运行上述程序时,输出为

2018/10/08 17:11:28 [debug] 8851#0: *2 Sent 8/8 bytes.
2018/10/08 17:11:28 [debug] 8851#0: *2 Session: Staging 8 bytes in thread buffer.
2018/10/08 17:11:33 [debug] 8851#0: *36 GET_REGION_INFO: Staging 99 bytes in thread buffer.
2018/10/08 17:11:33 [debug] 8851#0: *36 Sent 99/99 bytes.
2018/10/08 17:11:33 [debug] 8851#0: *36 Session: Staging 8 bytes in thread buffer.
2018/10/08 17:11:33 [debug] 8851#0: *36 Sent 8/8 bytes.
2018/10/08 17:11:38 [debug] 8851#0: *22 Receiving 8 bytes
2018/10/08 17:11:38 [debug] 8851#0: *22 Session: Staging 8 bytes in thread buffer.

在给定的示例输入中,带有[GET..]($req_mark)的行上的request-id 为*36.

为了更好的测试,*36要跳过(线路周围[GET..])的默认行数设置为两(2); 这可以在调用时更改.

*36在该行之前的输出中没有行[GET...](相反,该行所在的位置),因为数据中只有两个并且它们被适当地跳过; 并且,省略*36[GET..]在行之后的前两行(该行所在的行),然后打印其余行.这是预期的产出.

当我公司供应的跳跃距离(设定$skip_dist)的10输出是

2018/10/08 17:11:28 [debug] 8851#0: *2 Sent 8/8 bytes.
2018/10/08 17:11:28 [debug] 8851#0: *2 Session: Staging 8 bytes in thread buffer.
2018/10/08 17:11:38 [debug] 8851#0: *22 Receiving 8 bytes
2018/10/08 17:11:38 [debug] 8851#0: *22 Session: Staging 8 bytes in thread buffer.

正如预期的那样:在此样本数据中,*36在行前后都有少于10行[GET..],因此不会*36打印任何行.


原始帖子(相信*36是给定的感兴趣的请求ID)

将这些*36行存储在缓冲区中,当您在该区域中测试该短语时.一旦您离开该区域,请检查该短语是否已找到并相应地打印

my $trigger     = 'GET_REGION_INFO';
my $region_mark = '*36';

my (@buff, $drop_lines_mark);

while (<$fh>) {
    my ($req_id, $msg) = /.*?:\s*(\*[0-9]+)\s+(.*)/;
    if ($req_id eq $region_mark) {
        push @buff, $_  
        $drop_lines_mark = $#buff  if $msg =~ /$trigger/;
    }
    elsif (@buff) {              # just left region of interest
        if ($drop_lines_mark) { 
            for my $i (0..$#buff) {
                print $buff[$i] 
                    if $i < $drop_lines_mark-10 
                    or $i > $drop_lines_mark+10;
            }
        }
        else { print for @buff }

        $drop_lines_mark = '';
        @buff = ();
        print;      # don't forget the current line
    }
    else { print }
}
Run Code Online (Sandbox Code Playgroud)

未经测试的代码.