在大文件中使用后引用进行多行搜索

时间:2014-02-25 19:54:09

标签: regex linux perl sed multiline

假设我有一个大文本文件,我希望根据以下示例获取条目:

DATE TIME some uninteresting text,IDENTIFIER,COMMAND,ADDRESS,some uninteresting text
... some other lines in between ...
DATE TIME some uninteresting text DBCALL some uninteresting text IDENTIFIER some uninteresting text
... some other lines in between ...
DATE TIME some uninteresting text PARAM[1]=PARAM1 some uninteresting text IDENTIFIER some uninteresting text
...

包含2个条目的样本:

2014-02-25 09:13:57.765 CET [----s-d] [TL]  [DETAILS:22,6,W1OOKol6IF2DImfgVgJikUb,action_login1,10.1.1.1,n/a,n/a,Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0E)]
2014-02-25 09:13:57.819 CET [------d] [DB] execute {call PKG1.Proc1(?,?,?,?,?,?) } [DETAILS:W1OOKol6IF2DImfgVgJikUb]
2014-02-25 09:13:57.819 CET [------d] [DB] param[1]=loginname1 [DETAILS:W1OOKol6IF2DImfgVgJikUb]

2014-02-25 09:17:17.086 CET [----s-d] [TL]  [DETAILS:22,13,l3Na0H2bNOTv4AiaelSOS97,action_login1,10.1.1.1,n/a,n/a,Mozilla/5.0 (Windows NT 5.1; rv:27.0) Gecko/20100101 Firefox/27.0]
2014-02-25 09:17:17.087 CET [------d] [DB] execute {call PKG1.Proc1(?,?,?,?,?,?) } [DETAILS:l3Na0H2bNOTv4AiaelSOS97]
2014-02-25 09:17:17.087 CET [------d] [DB] param[1]=loginname1 [DETAILS:l3Na0H2bNOTv4AiaelSOS97]

已知变量

  • COMMAND =" action_login1"

  • DBCALL ="致电PKG1.Proc1"

  • PARAM1(例如param [1]的值)=" loginname1"

在运行时确定的变量

  • IDENTIFIER(例如会话ID)=" W1OOKol6IF2DImfgVgJikUb" (实施例)

  • ADDRESS(例如IP地址)=" 10.1.1.1" (实施例)

  • DATE =" 2014-02-25" (实施例)

  • TIME =" 09:13:57.765" (实施例)

用于定位相关行的变量

  • IDENTIFIER(例如会话ID)=" W1OOKol6IF2DImfgVgJikUb" (例子)

预期输出

对于这3行中的每一组:

  • 日期时间地址PARAM1 IDENTIFIER COMMAND

预期输出的样本(对于上面显示的2个样本记录:

2014-02-25 09:13:57.765 10.1.1.1 loginname1 W1OOKol6IF2DImfgVgJikUb action_login1
2014-02-25 09:17:17.086 10.1.1.1 loginname1 l3Na0H2bNOTv4AiaelSOS97 action_login1

订购和复杂性

  • 保证显示这3行的顺序

  • 这3个之间还有另外一行,这3个只是重要的一行

  • 这三条线通常彼此相距很远,通常它们都适合2-4kB块(例如,当没有找到其他相关线时,无需搜索文件末尾在几个KB内)

  • 输入文件可能非常大,无法完全读入内存

  • 不保证每个条目中不存在相同类型的其他条目(或者甚至只是它们的部分 - 仅1或2行)(第1行和第3行之间的块) ,就像下面这个简单例子中的情况一样。


2014-02-25 09:13:57.765 CET [----s-d] [TL]  [DETAILS:22,6,W1OOKol6IF2DImfgVgJikUb,action_login1,10.1.1.1,n/a,n/a,Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0E)]
2014-02-25 09:17:17.086 CET [----s-d] [TL]  [DETAILS:22,13,l3Na0H2bNOTv4AiaelSOS97,action_login1,10.1.1.1,n/a,n/a,Mozilla/5.0 (Windows NT 5.1; rv:27.0) Gecko/20100101 Firefox/27.0]
2014-02-25 09:17:17.087 CET [------d] [DB] execute {call PKG1.Proc1(?,?,?,?,?,?) } [DETAILS:l3Na0H2bNOTv4AiaelSOS97]
2014-02-25 09:17:17.087 CET [------d] [DB] param[1]=loginname1 [DETAILS:l3Na0H2bNOTv4AiaelSOS97]
2014-02-25 09:13:57.819 CET [------d] [DB] execute {call PKG1.Proc1(?,?,?,?,?,?) } [DETAILS:W1OOKol6IF2DImfgVgJikUb]
2014-02-25 09:13:57.819 CET [------d] [DB] param[1]=loginname1 [DETAILS:W1OOKol6IF2DImfgVgJikUb]

数量

  • 第1行:lot(所有用户的所有此类操作都有此类条目)

  • 第二行:很多(与第一行相同)

  • 第3行:很少(所有数据库调用中只有一小部分匹配)

  • 完整输出也可能非常大,我们不能依赖于将其完全保留在内存中的可能性

目标

  1. 搜索具有相应DBCALL值的行(示例中的第2行),获取其IDENTIFIER
  2. 对于该IDENTIFIER,使用适当的PARAM [1] = PARAM1(示例中的第3行)找到最近的行(下面某处,通常它只是以下行但不总是)。如果未找到任何内容,则中止此循环并继续在步骤1中搜索文件的其余部分。
  3. 对于该IDENTIFIER,使用相应的COMMAND(示例中的第1行)找到最近的行(上面某处)。如果未找到任何内容,则中止此循环并继续在步骤1中搜索文件的其余部分。
  4. 打印该行的日期,时间,地址,标识符,命令(示例中的第一行)
  5. 在最坏的情况下,它可能简化为只涉及2行而不是全部3行,为了更好的可靠性,订单会略有不同:

    1. 使用相应的COMMAND(示例中的第1行)搜索该行,获取其IDENTIFIER。
    2. 对于该IDENTIFIER,使用适当的PARAM [1] = PARAM1值(示例中的第2行)找到最近的行(下面某处)。如果未找到任何内容,则中止此循环并继续在步骤1中搜索文件的其余部分。
    3. 打印该行的日期,时间,地址,标识符,命令(示例中的第1行)。
    4. 我设法通过逐块读取文件,然后使用后引用进行多行正则表达式搜索,例如或多或少地在perl中工作(更简单的第二种情况)。有类似的东西:

      # read 4kB at a time
      local $/ = \4096;
      ...
      my $searchpattern = qr/(\d*-\d*-\d*\s\d*:\d*:\d*\.\d*).*?,(\w*?),$command,.*?param\[1\]=$param1.*?\[ID:(\2)\]/ms;
      ...
      

      然而问题是有一些错过的匹配 - 那些不完全适合perl读取和处理的单个块内的匹配。 由于文件非常大,我无法将其全部读入内存。 增加块的大小不是解决方案,因为由于块开始/结束的定位,总会有一些情况可以跨越多个块。

      有没有人知道如何有效地解决这个问题(特别是速度和记忆)?

      我也尝试过awk或sed,但由于多线处理等的后向引用(引用相同的IDENTIFIER)限制,因此无法使它们正常工作,例如基于此的东西没有用:

      sed -n '/1st line pattern(match-group-1).../,/3rd line pattern\1.../p'
      

      因为第一个模式的后向参考不能用于第二个模式。 此外,sed甚至会打印我不感兴趣的条目 - 一旦找到第一个匹配的开始模式行,它将打印所有内容,直到找到结束模式,如果找不到结束模式在所有(是的,可能发生),它将打印所有内容,直到文件结束。这也是我不想发生的事情。

      修改 添加了更好的输入样本,明确说明

      注意:

      • glenn jackman展示的AWK解决方案非常好并且运行良好(非常感谢),但它并没有涵盖可能的复杂性问题(一个块内的多个混合条目等)。它只有在每次都有这3行的干净块时才有效。因此不幸的是它可能会遗漏一些条目。下面这个解决方案的例子,它应该用2个参数执行:1。PARAM1(loginname1),2。input-file


      #!/bin/sh
      if [ "$#" -ne 2 ]; then
        echo "Usage: $0 loginname logfile" >&2
        exit 1
      fi
      
      awk -v dbparam="$1" -v cmd="action_login1" -v dbcall="call PKG1.Proc1" '
          $0 ~ ","cmd"," {
              match($0, /^([0-9]+-[0-9]+-[0-9]+)\ ([0-9]+:[0-9]+:[0-9]+.[0-9]+)/, matches);
              date = matches[1];
              time = matches[2];
              match($0, "DETAILS:[0-9]+,[0-9]+,(.*?),"cmd",([0-9]+.[0-9]+.[0-9]+.[0-9]+),", matches);
              sessionid = matches[1];
              ipaddress = matches[2];
              seen_command = 1;
              seen_dbcall = 0;
          }
          seen_command && $0 ~ dbcall && $0 ~ "\\[DETAILS:"sessionid {
              seen_dbcall = 1;
          }
          seen_dbcall && $0 ~ "param\\[1\\]="dbparam && $0 ~ "\\[DETAILS:"sessionid {
              print date, time, ipaddress, sessionid, cmd;
              seen_command = 0;
              seen_dbcall = 0;
          }
      ' $2
      
      • sgauria的建议可能就是这样,但是当我不能依赖将所有中间数据存储到内存中时如何有效地做到这一点?

2 个答案:

答案 0 :(得分:1)

我喜欢awk来实现这种状态机。类似的东西:

awk -v cmd="$command" -v param="$param1" -v dbcall="$dbcall" '
    $0 ~ ","cmd"," {
        datetime    = parse_datetime_from_line()
        identifier  = parse_identifier_from_line()
        address     = parse_address_from_line()
        seen_command = 1
        seen_dbcall = 0
    }
    seen_command && $0 ~ dbcall {
        seen_dbcall = 1
    }
    seen_dbcall && $0 ~ param1 {
        print datetime, address, identifier, command
        seen_command = 0
        seen_dbcall = 0
    }
' file

您没有具体描述输入文件,因此从行中提取重要元素仍然是一个练习。

答案 1 :(得分:1)

使用Perl,您可以使用散列在文件中传递一次:

#!/usr/bin/perl

use strict;
use warnings;
use 5.010;

use Regexp::Common qw(net time);

my $dbcall  = 'call PKG1.Proc1';
my $command = 'action_login1';
my $param1  = 'loginname1';

# Time::Format-compatible pattern
my $date_format = 'yyyy-mm-dd hh:mm{in}:ss.mmm';

my $command_regex = qr/^($RE{time}{tf}{-pat => $date_format}).*\[DETAILS:\d+,\d+,(\w+),$command,($RE{net}{IPv4}),/;
my $dbcall_regex  = qr/execute {$dbcall\(.*\) } \[DETAILS:(\w+)\]/;
my $param1_regex  = qr/param\[1\]=$param1 \[DETAILS:(\w+)\]/;

my %hash;
while (<DATA>) {
    if (/$command_regex/) {
        $hash{$2} = {
            date => $1,
            ip   => $3
        };
    }
    elsif (/$dbcall_regex/) {
        $hash{$1}{seen} = 1;
    }
    elsif (/$param1_regex/) {
        if (exists $hash{$1}{seen}) {
            say join ' ', $hash{$1}{date}, $hash{$1}{ip}, $param1, $1, $command;
            delete $hash{$1};
        }
    }
}

__DATA__
2014-02-25 09:13:57.765 CET [----s-d] [TL]  [DETAILS:22,6,W1OOKol6IF2DImfgVgJikUb,action_login1,10.1.1.1,n/a,n/a,Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0E)]
2014-02-25 09:13:57.819 CET [------d] [DB] execute {call PKG1.Proc1(?,?,?,?,?,?) } [DETAILS:W1OOKol6IF2DImfgVgJikUb]
2014-02-25 09:13:57.819 CET [------d] [DB] param[1]=loginname1 [DETAILS:W1OOKol6IF2DImfgVgJikUb]
2014-02-25 09:17:17.086 CET [----s-d] [TL]  [DETAILS:22,13,l3Na0H2bNOTv4AiaelSOS97,action_login1,10.1.1.1,n/a,n/a,Mozilla/5.0 (Windows NT 5.1; rv:27.0) Gecko/20100101 Firefox/27.0]
2014-02-25 09:17:17.087 CET [------d] [DB] execute {call PKG1.Proc1(?,?,?,?,?,?) } [DETAILS:l3Na0H2bNOTv4AiaelSOS97]
2014-02-25 09:17:17.087 CET [------d] [DB] param[1]=loginname1 [DETAILS:l3Na0H2bNOTv4AiaelSOS97]

输出:

2014-02-25 09:13:57.765 10.1.1.1 loginname1 W1OOKol6IF2DImfgVgJikUb action_login1
2014-02-25 09:17:17.086 10.1.1.1 loginname1 l3Na0H2bNOTv4AiaelSOS97 action_login1

由于设置了行的相对顺序,我们可以在打印后从哈希中删除相应的条目,从而保持较低的内存使用率。