Stijn De Schutter
Stijn De Schutter

Reputation: 35

Perl: parsing text output into array of hashes

i'm not experienced and not very good at writing code and I have been stuck on this issue for a little while now. I have searched stackoverflow before but couldn't really find anything that matches my issue so thought of trying to get some help this way.

I want to build a script that analyzes text output from a certain command and put that into an array of hashes so i can do stuff with it.

I have text output like this:

      1 : DISPLAY AUTHREC GROUP('GROUP1')
 AMQ8864I: Display authority record details.
    PROFILE(SYSTEM.ADMIN.COMMAND.QUEUE)     ENTITY(GROUP1)
    ENTTYPE(GROUP)                          OBJTYPE(QUEUE)
    AUTHLIST(BROWSE,CHG,CLR,DLT,DSP,GET,INQ,PUT)
 AMQ8864I: Display authority record details.
    PROFILE(SYSTEM.MQEXPLORER.REPLY.MODEL)
    ENTITY(GROUP1)                         ENTTYPE(GROUP)
    OBJTYPE(QUEUE)
    AUTHLIST(BROWSE,CHG,CLR,DLT,DSP,GET,INQ,PUT)
 AMQ8864I: Display authority record details.
    PROFILE(self)                           ENTITY(GROUP1)
    ENTTYPE(GROUP)                          OBJTYPE(QMGR)
    AUTHLIST(ALTUSR,CHG,CONNECT,DLT,DSP,INQ,SET)

I want to store each record as a hash on an array of hashes. this is how i currently do it (please don't be negative about how i wrote this, there are probably 100 better ways to do this but i'm still learning so keep that in mind please, suggestions to improve are always welcome) :

    my @authrecs;   
    my @authrecoutput;  #this array contains the text output as displayed above, each line in a different element. 
    
    my $len = @authrecoutput;
    for (my $i = 0; $i <= $len; $i++){
        if(@authrecoutput[$i] =~ /^AMQ8864I/)  #when the eventcode occurs i start creating a new hash and store all properties in it. 
        {
            my $rec = {};
            while (@authrecoutput[$i+1] =~ /(\S+)\((.+?)\)/g ){
                $rec->{$1} = $2;
            }
            while (@authrecoutput[$i+2] =~ /(\S+)\((.+?)\)/g ){ 
                $rec->{$1} = $2;
            }
            while (@authrecoutput[$i+3] =~ /(\S+)\((.+?)\)/g ){  #problem with this that now the script only looks at $i+3 lines so anything on $1+4 i will miss.  
                $rec->{$1} = $2;
            }
            push @authrecs, $rec; 
        }
    
    }

    print Dumper @authrecs;

my output:

$VAR1 = {
          'AUTHLIST' => 'BROWSE,CHG,CLR,DLT,DSP,GET,INQ,PUT,PASSALL,PASSID,SET,SETALL,SETID',
          'ENTTYPE' => 'GROUP',
          'ENTITY' => 'GROUP1',
          'OBJTYPE' => 'QUEUE',
          'PROFILE' => 'SYSTEM.ADMIN.COMMAND.QUEUE'
        };
$VAR2 = {
          'PROFILE' => 'SYSTEM.MQEXPLORER.REPLY.MODEL',
          'ENTITY' => 'GROUP1',
          'OBJTYPE' => 'QUEUE',
          'ENTTYPE' => 'GROUP'
        };
$VAR3 = {
          'PROFILE' => 'self',
          'OBJTYPE' => 'QMGR',
          'ENTITY' => 'GROUP1',
          'ENTTYPE' => 'GROUP',
          'AUTHLIST' => 'ALTUSR,CHG,CONNECT,DLT,DSP,INQ,SET,SETALL,SETID,CTRL,SYSTEM'
        };

This works but the problem is that looking for the properties in the array is not dynamic and is set at a fixed length of @authrecoutput[$i+3]. For example, the 'SYSTEM.MQEXPLORER.REPLY.MODEL' profile has 4 lines in the text output and the fourth line including the 'AUTHLIST'property is obviously not captured.

I tried nesting a second for loop which breaks when the eventcode AMQ8864I occurs again (because that's when I know a new record is being displayed) but i didn't find how to break a for loop on a regex condition, if that is even a thing.

How could i code this to be more dynamically and reads lines until the next occurrence of 'AMQ8864I' is detected.

Upvotes: 1

Views: 197

Answers (2)

Polar Bear
Polar Bear

Reputation: 6808

Utilization of match range operator provides other approach to the solution.

use strict;
use warnings;
use feature 'say';

use Data::Dumper;

my $id = 'AMQ8864I';
my(%data, $record);

while( <DATA> ) {
    if( /^ $id:/ .. /^\s+AUTHLIST/ ) {
        unless( /^ $id:/ ) {
            $record->{$1} = $2 while /\s+(.+?)\((.*?)\)/g;
        } else {
            push @{$data{$id}}, $record if defined $record;
            $record = undef;
        }
    }
}

push @{$data{$id}}, $record if defined $record;

say Dumper(\%data);

__DATA__
     1 : DISPLAY AUTHREC GROUP('GROUP1')
 AMQ8864I: Display authority record details.
    PROFILE(SYSTEM.ADMIN.COMMAND.QUEUE)     ENTITY(GROUP1)
    ENTTYPE(GROUP)                          OBJTYPE(QUEUE)
    AUTHLIST(BROWSE,CHG,CLR,DLT,DSP,GET,INQ,PUT)
 AMQ8864I: Display authority record details.
    PROFILE(SYSTEM.MQEXPLORER.REPLY.MODEL)
    ENTITY(GROUP1)                         ENTTYPE(GROUP)
    OBJTYPE(QUEUE)
    AUTHLIST(BROWSE,CHG,CLR,DLT,DSP,GET,INQ,PUT)
 AMQ8864I: Display authority record details.
    PROFILE(self)                           ENTITY(GROUP1)
    ENTTYPE(GROUP)                          OBJTYPE(QMGR)
    AUTHLIST(ALTUSR,CHG,CONNECT,DLT,DSP,INQ,SET)

Output

$VAR1 = {
          'AMQ8864I' => [
                          {
                            'ENTTYPE' => 'GROUP',
                            'AUTHLIST' => 'BROWSE,CHG,CLR,DLT,DSP,GET,INQ,PUT',
                            'OBJTYPE' => 'QUEUE',
                            'PROFILE' => 'SYSTEM.ADMIN.COMMAND.QUEUE',
                            'ENTITY' => 'GROUP1'
                          },
                          {
                            'ENTTYPE' => 'GROUP',
                            'ENTITY' => 'GROUP1',
                            'OBJTYPE' => 'QUEUE',
                            'PROFILE' => 'SYSTEM.MQEXPLORER.REPLY.MODEL',
                            'AUTHLIST' => 'BROWSE,CHG,CLR,DLT,DSP,GET,INQ,PUT'
                          },
                          {
                            'ENTTYPE' => 'GROUP',
                            'AUTHLIST' => 'ALTUSR,CHG,CONNECT,DLT,DSP,INQ,SET',
                            'OBJTYPE' => 'QMGR',
                            'PROFILE' => 'self',
                            'ENTITY' => 'GROUP1'
                          }
                        ]
        };

Upvotes: 1

choroba
choroba

Reputation: 241988

I use a variable $inside as a flag that tells me whether I'm inside a record or not. If I am, I store the tuples into the last hash in the array. When a new section starts, I push an empty hash into the array.

#!/usr/bin/perl
use warnings;
use strict;

my @arr;
my $inside;
while (<DATA>) {
    $inside = 0 unless /^ {4}/;

    if ($inside) {
        $arr[-1]{$1} = $2 while / (\S+)\(([^)]+)\)/g;
    }

    if (/^ AMQ8864I:/) {
        $inside = 1;
        push @arr, {};
    }
}
use Data::Dumper;
print Dumper \@arr;

__DATA__
...

Output:

$VAR1 = [
          {
            'ENTTYPE' => 'GROUP',
            'AUTHLIST' => 'BROWSE,CHG,CLR,DLT,DSP,GET,INQ,PUT',
            'OBJTYPE' => 'QUEUE',
            'PROFILE' => 'SYSTEM.ADMIN.COMMAND.QUEUE',
            'ENTITY' => 'GROUP1'
          },
          {
            'OBJTYPE' => 'QUEUE',
            'ENTITY' => 'GROUP1',
            'PROFILE' => 'SYSTEM.MQEXPLORER.REPLY.MODEL',
            'AUTHLIST' => 'BROWSE,CHG,CLR,DLT,DSP,GET,INQ,PUT',
            'ENTTYPE' => 'GROUP'
          },
          {
            'ENTITY' => 'GROUP1',
            'OBJTYPE' => 'QMGR',
            'PROFILE' => 'self',
            'AUTHLIST' => 'ALTUSR,CHG,CONNECT,DLT,DSP,INQ,SET',
            'ENTTYPE' => 'GROUP'
          }
        ];

Upvotes: 4

Related Questions