regex - 大文件中带有反向引用的多行搜索

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

包含两个条目的示例:
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=“操作登录1”
dbcall=“调用pkg1.proc1”
PARAM1(例如PARAM〔1〕的值)=“LogiNAME1”
运行时确定的变量
标识符(例如会话ID)=“w1ookol6if2dimfgvgjikub”(示例)
地址(例如IP地址)=“10.1.1.1”(示例)
date=“2014-02-25”(示例)
time=“09:13:57.765”(示例)
用于定位相关行的变量
标识符(例如会话ID)=“w1ookol6if2dimfgvgjikub”(示例)
预期产量
对于这三条线中的每一组:
日期时间地址PARAM1标识符命令
预期输出的样本(对于上面所示的两个样本记录:
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

排序与复杂性
保证这三条线的显示顺序
这三条线之间还有另外一条线,这三条线就是这里最重要的
这三行通常彼此距离不远,通常它们都适合2-4KB块(例如,当在几KB内找不到其他相关行时,在文件结束之前无需搜索)
输入文件可能很大,无法完全读入内存
不能保证在每个条目中(第一行和第三行之间的块)不会有其他类型相同的条目(甚至只是它们的部分-只有1或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: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行:批(所有用户的此类操作都有此类条目)
2号线:地段(与1号线相同)
第三行:很少(所有数据库调用中只有一小部分匹配)
完整的输出也可能非常大,我们不能指望它完全保存在内存中
目标
搜索具有适当dbcall值的行(示例中的第2行),获取其标识符
对于该标识符,用适当的PARAM〔1〕=PARAM1(在该示例中为第三行)找到最近的行(在下面的某个地方,通常只是下面的行,但不总是)。如果未找到任何内容,则中止此循环,并在步骤1继续搜索文件的其余部分。
对于该标识符,使用适当的命令(示例中的第1行)找到最近的行(在上面的某处)。如果未找到任何内容,则中止此循环,并在步骤1继续搜索文件的其余部分。
打印该行的日期、时间、地址、标识符、命令(示例的第1行)
在最坏的情况下,可能可以简化为只包含2条线而不是全部3条线,为了更好的可靠性,顺序将略有不同:
使用适当的命令搜索行(示例中的第1行),获取其标识符。
对于该标识符,查找最近的行(在下面的某个位置),并使用相应的参数1(=第二)。如果未找到任何内容,则中止此循环,并在步骤1继续搜索文件的其余部分。
打印该行的日期、时间、地址、标识符、命令(示例的第1行)。
我设法使它(或多或少)在Perl中工作(更简单的第二种情况),方法是按块读取文件,然后使用back reference进行多行regex搜索,例如使用以下内容:
# 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,但由于多行处理的反向引用(引用同一标识符)限制等原因,无法使它们正常工作,例如基于此的某些东西不起作用:
sed -n '/1st line pattern(match-group-1).../,/3rd line pattern\1.../p'

因为第一个模式的后向引用不能用于第二个模式。
此外,sed甚至会打印我不感兴趣的条目-一旦它找到第一行匹配的开始模式,它将打印所有内容,直到找到结束模式,如果根本找不到结束模式(是的,这可能发生),它将打印所有内容,直到文件结束。这也是我不想发生的事。
编辑:
添加了更好的输入示例,澄清了描述
笔记:
glenn jackman所示的解决方案非常好,效果很好(非常感谢),但它不包括可能的复杂性问题(一块内的多个混合条目等)。只有当这三条线每次都有干净的方块时,它才起作用。因此,不幸的是,它可能会漏掉一些条目。下面这个解决方案的例子,它应该用2个参数执行:1。PARAM1(LogiNAME1),2。输入文件
#!/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的建议可能是这样的,但是当我不能依赖于将所有中间数据存储到内存中时,如何有效地做到这一点呢?

最佳答案

使用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

因为行的相对顺序是设置好的,所以我们可以在打印散列后从散列中删除相应的条目,从而保持较低的内存使用率。

关于regex - 大文件中带有反向引用的多行搜索,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22024742/

相关文章:

python - 单击桌面图标在 Raspbian 中执行 python 脚本

linux - 如何使用共享对象的调试版本

linux - "Warning: You need to have Ruby and Sass installed and in your PATH for this task to work."

perl - 使用 `bless` 创建具有继承的对象

javascript - 一个短语中多个短语的正则表达式

java - PHP 在 Java 中的 `preg_match_all` 功能

javascript - 可见文本的正则表达式,而不是 HTML

perl - 批量重命名目录中的文件名

perl - 如何将 undef 作为参数从 TT 模板传递给对象方法?

java - Java中\b边界匹配器的使用