【问题标题】:Capturing output with Perl, until a specific pattern is found使用 Perl 捕获输出,直到找到特定模式
【发布时间】:2012-03-09 10:31:00
【问题描述】:

我觉得我在这里遗漏了一些非常简单的东西,但这是我第一次需要这样做并且找不到示例。

我有一个巨大的foreach 循环,它遍历输出日志并根据匹配的正则表达式提取各种信息。我的主要问题是一些较大类型的输出有页眉和页脚,比如*** Begin bangle tracking log***,后面跟着几行乱码,然后是***End bangle tracking log***

有没有办法从foreach 循环中创建一个内部循环来存储所有行,直到找到页脚?

foreach my $line( @parseme )
{
    if( $line =~ m/***Begin bangle tracking log***/ )
    {
        #Help! Push all lines into an array until bangle tracking footer is found.
    }
    if( $line =~ m/Other stuff I am tracking/ )
    {
        #Do other things
    }
}

【问题讨论】:

    标签: regex perl parsing


    【解决方案1】:

    您可以使用range operator,它在标量上下文中充当触发器:

    foreach ( @parseme ) {
        if ( /Begin bangle tracking log/ .. /End bangle tracking log/ ) {
            push @array, $_;
        }
        # other stuff...
    }
    

    我将$_ 用于foreach 循环,因为它允许更简洁的语法。如果你愿意,你可以使用另一个变量,但是你必须把条件写成这样:

    if ( $line =~ /Begin .../ .. $line =~ /End .../ ) {
    

    加上一些额外的括号可能更易读:

    if ( ($line =~ /Begin .../) .. ($line =~ /End .../) ) {
    

    关于触发器运算符的一个需要注意的问题是,即使在循环结束后它也会记住它的状态。这意味着,如果您打算再次运行循环,您真的应该确保@parseme 数组以匹配/End .../ 正则表达式的行结束,以便触发器下次循环开始时将处于已知状态。

    编辑:根据下面 DVK 的评论,如果您想在到达页脚行后立即处理收集的行,您可以通过检查 .. 运算符的返回值来实现,最后一行以E0 结尾:

    foreach ( @parseme ) {
        my $in_block = /Begin bangle tracking log/ .. /End bangle tracking log/;
        if ( $in_block ) {
            push @array, $_;
        }
        if ( $in_block =~ /E0$/ ) {  # last line
            # process the lines in @array
            @array = ();
        }
        # other stuff...
    }
    

    【讨论】:

    • 但是一旦到达 END,你的代码会变得僵硬吗? @array 将被丢弃或无限附加!
    • @DVK:我假设 OP 想要收集数组中的行并在循环后处理它们。一旦达到结束条件就可以处理它们,但这确实会使代码复杂一点。
    • 我已经使用范围运算符实现了我的解析器/处理器,它完美地解决了我的问题。感谢您的帮助!
    【解决方案2】:

    您可以通过实现一个原始状态机轻松地做到这一点:

    my $inside_bangle = 0; # 0=outside block, 1=inside
    my @buffer;
    foreach my $line( @parseme ) {
        if  ($line =~ m/***Begin bangle tracking log***/ ) {
            $inside_bangle = 1;
            next;
        }
        if ($line =~ m/***End bangle tracking log***/ ) {
            $inside_bangle = 0;
            # PROCESS @buffer somehow
            next;
        }
        if ($inside_bangle) {
            push @buffer, $line;
            next;
        }
        if ($line =~ m/other stuff i am tracking/ ) {
            #Do other things
        }
    }
    

    另一种选择是使用触发器 (..)

    【讨论】:

    • @sarnold - 为什么这么难?一旦你修复了 Ilmari 的解决方案来处理缓冲区数组,它几乎是一样的。
    • 好的,两态状态机并不真的困难;但通用技术的用处远不止简单的.. 运算符。
    • @sarnold - 你是对的。这也是我喜欢这项技术的原因之一。
    【解决方案3】:

    您可能正在寻找.. 运算符,它在与正则表达式一起应用时具有一些神奇的属性。下面的例子是从PLEAC偷来的:

    while (<>) {
        if (/BEGIN PATTERN/ .. /END PATTERN/) {
            # line falls between BEGIN and END in the
            # text, inclusive.
        }
    }
    

    在块中,按照您认为合适的方式附加到您的数组变量。

    【讨论】:

    • 那里的正则表达式没有什么特别之处; if (($_ eq "BEGIN") .. ($_ eq "END")) { 之类的东西也可以。正是标量上下文使.. 充当触发器而不是列表范围生成器。 (但是,带有 constant 操作数 的标量 .. 的行为是 魔术:在标量上下文中 5 .. 7 等效于 ($. == 5) .. ($. == 7)。)
    • 哦,酷;我只见过以这种方式与.. 一起使用的行号和正则表达式。谢谢@IlmariKaronen。
    【解决方案4】:

    现在你可以存储你的日志 sn-p 的多个实例,如果它出现不止一次(DVK 的原始代码):

    my $inside_bangle = 0; # 0=outside block, 1=inside
    my %buffer;
    my $index = 0;
    foreach my $line( @parseme ) {
        if  ($line =~ m/***Begin bangle tracking log***/ ) {
            $inside_bangle = 1;
            next;
        }
        if ($line =~ m/***End bangle tracking log***/ ) {
            $inside_bangle = 0;
            $index++;
            # PROCESS @buffer somehow
            next;
        }
        if ($inside_bangle) {
            push @{ $buffer{$index} }, $line;
            next;
        }
        if ($line =~ m/other stuff i am tracking/ ) {
            #Do other things
        }
    }
    

    这是我最初写的,但我认为 DVK 的代码更具可读性和简洁性:

    open FILE, "<", 'testfile.log';
    @parseme = <FILE>;
    my $initialize = shift @parseme;
    my $startLogging = $initialize =~ m/^Log Start$/ ? 1 : 0; # test if first line of array is start of log
    my %storage = ();
    my $index = 0;
    foreach my $line (@parseme) {
     $startLogging = 1 if $line =~ m/^Log Start$/;
     if ($startLogging == 1) {
      push( @{ $storage{$index} }, $line ) unless $line =~ m/(Log Start|Log End)$/;
      if ($line =~ m/^Log End$/) {
       $startLogging = 0;
       $index++;
      }
     }
    }
    

    【讨论】:

    • 如果你打算从另一个示例中几乎批发地窃取代码,明确承认这一点是有礼貌的。顺便说一句,如果您简单地按原样使用我的代码并执行 %buffer{$index++} = @buffer; 而不是 # PROCESS @buffer somehow ,那么您的解决方案会容易 100%
    • 道歉 DVK,我的意思是只添加修改,而不是窃取您的代码,是的,%buffer{$index++} = @buffer; 也是一个不错的技巧。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-14
    • 2021-12-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 1970-01-01
    • 2016-08-06
    相关资源
    最近更新 更多