Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capturing output with Perl, until a specific pattern is found

I feel like I am missing something very simple here, but this is the first time I've needed to do this and am having trouble finding an example.

I have a giant foreach loop that walks through output logs and extracts various bits of information based on matching regular expressions. My main problem is that a few larger types of output have a header and footer, like *** Begin bangle tracking log*** followed by several lines of gibberish and then a ***End bangle tracking log***.

Is there a way, from within a foreach loop, to have an inner loop that stores all the lines until a footer is found?

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
    }
}
like image 609
Pacman Avatar asked Dec 27 '22 05:12

Pacman


2 Answers

You could use the range operator, which acts as a flip-flop in scalar context:

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

I used $_ for the foreach loop because it allows for more concise syntax. You can use another variable if you like, but then you'll have to write the condition as something like:

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

which might be more readable with some extra parentheses:

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

One issue to note about the flip-flop operator is that it remembers its state even after the loop ends. This means that, if you intend to run the loop again, you really ought to make sure that the @parseme array ends with a line that matches the /End .../ regexp, so that the flip-flop will be in a known state when the loop starts the next time.

Edit: Per DVK's comment below, if you want to process the collected lines as soon as you reach the footer line, you can do that by checking the return value of the .. operator, which will end with E0 on the last line:

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...
}
like image 183
Ilmari Karonen Avatar answered Feb 06 '23 07:02

Ilmari Karonen


You can do that somewhat easily by implementing a primitive state machine:

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
    }
}

Another option is to use flip-flop (..)

like image 41
DVK Avatar answered Feb 06 '23 07:02

DVK