regex 使用可选字段解析日志文件

amrnrhlw  于 2023-02-14  发布在  其他
关注(0)|答案(4)|浏览(149)

我想读取一个日志文件并将其拆分为四个标量。
这是一个日志文件示例:

[time1] [error1] [who is1] mess is here1
[time2] [error2] mess is here2

我想把这些标量作为输出:

($time, $err, $who, $mess)=('time1', 'error1', 'who is1', 'mess is here1')
($time, $err, $who, $mess)=('time2', 'error2', '', 'mess is here2')

如何在Perl中实现?
这是我当前的代码,但它不工作:

while (<MYFILE>) {
    chomp;
    ($time, $err, $who, $mess)=($_ =~/\[([.]*)\] \[([.]*)\] (\[([.]*)\]|[ ])([.]*)/);
    $logi.= "<tr><td>$time</td><td>$err</td><td>$who</td><td>$mess</td></tr>\n";
}
vnzz0bqm

vnzz0bqm1#

这里有一种更易读的方法,利用编译后的正则表达式和/x标志来忽略空格。

my $block_re = qr{ \[ (.*?) \] }x;    # [some thing]
my $log_re = qr{^
    $block_re \s+ $block_re \s+ (?: $block_re \s+ )?  # two or three blocks
    (.*)                                              # the log message
$}x;

while($line = <$fh>) {
    my @fields = $line =~ $log_re;
    my $message = pop @fields;
    my($time, $err, $who) = @fields;

    print "time: $time, err: $err, who: $who, message: $message\n";
}

块正则表达式的关键之一是使用“非贪婪”匹配操作符.*?。通常.*将匹配最长的字符串,这意味着m{ \[ .* \] }x将匹配所有“[foo] [bar] [baz]",而不仅仅是“[foo]"。通过添加?告诉它是非贪婪的,它将匹配最短的字符串,即“[foo]"。
我所做的另一个修改是将最后一个字段而不是第四个字段作为消息字段,我想您的格式可以包含任意多的“[foo]”块。

rjjhvcjd

rjjhvcjd2#

my ($time, $err, $who, $mess)=($_ =~/\[(.*?)\]\s+\[(.*?)\]\s+(?:\[(.*?)\]|)\s*(.*)/);
$logi.= "<tr><td>$time</td><td>$err</td><td>".($who||"")."</td><td>$mess</td></tr>\n";

我们的想法是用[block]nothing 匹配第三个位置。

(?:\[(.*?)\]|)

如果这部分不匹配,$who保持未定义状态。打印输出中的$who||""是为了防止使用未定义值时出现警告。

epggiuax

epggiuax3#

我不同意其他答复建议使用非贪婪量词(即.*?)。假设]不是时间/错误/谁字段中的有效字符,则您 * 不是 * 在查找]后面的任何字符的最短可能序列。您正在查找非]字符的最长序列,正确地写为[^]]*。这样做既更高效(正则表达式引擎在看到]时可以立即停止,而不是潜在地做大量回溯来查找替代匹配),也更准确地向未来阅读代码的程序员传达您的意图。

#!/usr/bin/env perl

use strict;
use warnings;

while (<DATA>) {
  chomp;
  my ($time, $err, $who, $mess) =
    ($_ =~/\[([^]]*)\] \[([^]]*)\] (?:\[([^]]*)\] )?(.*)/);
  $who ||= '(unspecified)';
  print "$time - $err - $who - $mess\n";
}

__DATA__
[time1] [error1] [who is1] mess is here1
[time2] [error2] mess is here2

输出:

time1 - error1 - who is1 - mess is here1
time2 - error2 - (unspecified) - mess is here2

顺便说一句,初始正则表达式的核心问题并不是贪婪的匹配......您试图匹配[.]*,它将只匹配文字.字符序列;它在日志条目“[...] [..] [...]......."上运行良好,但对于包含除[]分隔符、分隔字段的空格和表示字段内容的点以外的字符的条目,根本不匹配。

9jyewag0

9jyewag04#

首先,它不起作用,因为正则表达式是错误的,你有\[([.]*)\]这样的东西,[.]是字面量.的字符类,这不是你要匹配的,相反,你需要\[(.*?)\],正如你在其他答案中看到的。
但是,我倾向于认为,如果有可选字段,最终就会有更多的可选字段,因此,我会解析它以获得可选数量的字段。
带有/g标志的\G锚可以让你在标量上下文中匹配一个字符串,并记住你在下一次匹配中停止的位置。/c标志可以让匹配失败而不重置那个匹配。这样,我可以迭代字符串,直到我用完[...]组,然后简单地抓取剩下的任何东西:

sub parse_line {
    local $_ = shift;

    my @items;

    LOOP: {
        if( /\G \s* \[ (.*?) \] \s*/gcx ) {
            push @items, $1;
            redo LOOP;
            }
        else {
            push @items, /\G \s* (.+)/gx;
            last LOOP;
            }
        }

    return @items;
    }

相关问题