删除给定匹配线周围的线

时间:2018-10-23 19:00:10

标签: 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.

基于包含[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

我能想到的唯一方法似乎容易出错,而且过于复杂。这个文件大约是一个千兆字节,因此我想整件事是为了 以避免...尽管该机器有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.

1 个答案:

答案 0 :(得分:5)

问题:在某些带有特定请求ID的行上有一个特定短语。该request-id中与该短语之间的任何距离都在给定距离之内的所有行以及该行本身都应被跳过。

我们不知道这是针对哪个请求ID的,整个文件中可能还会发生什么变化。

首先阅读,直到出现该短语,然后将行保存在缓冲区中。一旦我们从该行中解析了请求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;
}

在很多地方可以提高效率。

一个主要的效率问题涉及缓冲区大小的中心问题。在触发触发器之前可以收集多少数据?如果文件中只有几行[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)上的请求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的行之前和之后,少于[GET..]的行少于10行,因此没有打印*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 }
}

未经测试的代码。