How can I parse the output of the runmqsc command using Perl?
I am trying to develop a Perl regex to parse the command output from the IBM runmqsc utility.
Each line of interest contains one or more attribute / value pairs with the format: "ATTRIBUTE (VALUE)". The attribute value can be empty or contain parentheses. Typically, there are at most two attribute / value pairs appearing on a given line, so a regular expression is written in this assumption.
Example input in Perl RE:
CHANNEL(TO.IPTWX01) CHLTYPE(CLUSRCVR)
DISCINT(6000) SHORTRTY(10)
TRPTYPE(TCP) DESCR( )
LONGTMR(1200) SCYEXIT( )
CONNAME(NODE(1414)) MREXIT( )
MREXIT( ) CONNAME2(SOME(1416))
TPNAME( ) BATCHSZ(50)
MCANAME( ) MODENAME( )
ALTTIME(00.41.56) SSLPEER()
CONTRIVED() ATTR (00-41-56)
CONTRIVED() DOCTORED()
MSGEXIT( )
I have the following Perl code to capture each attribute / value pair.
Perl code
my $resplit = qr/\s+([^\s]+(?:\([^)]*\))?)\s?/;
while ( <IN2> )
{ s/[\s\r\n]+$//;
if ( m/^\s(?:$resplit)(?:$resplit)?$/ )
{ my ($one,$two) = ($1,$2);
print "one: $one, two: $two\n";
}
}
Here's the output when the above code is applied to sample input:
one: CHANNEL (TO.IPTWX01), two: CHLTYPE (CLUSRCVR) one: DISCINT (6000), two: SHORTRTY (10) one: TRPTYPE (TCP), two: DESCR () one: LONGTMR (1200), two: SCYEXIT () one: CONNAME (NODE (1414)), two: MREXIT () one: MREXIT (), two: CONNAME2 (SOME (1416)) one: TPNAME (), two: BATCHSZ (50) one: MCANAME (), two: MODENAME () one: ALTTIME (00.41.56), two: SSLPEER () one: CONTRIVED (), two: ATTR (00-41-56) one: CONTRIVED (), two: DOCTORED () one: MSGEXIT (, two:)
This works great except for the last line in the output above. I'm really trying to figure out how to change the above $ resplit expression to capture the latter case.
Can anyone suggest any ideas / suggestions on how to make this work or a different approach?
The Text :: Balanced module addresses this problem. This approach will handle any number of columns.
use strict;
use warnings;
use Text::Balanced qw(extract_bracketed);
my ($extracted, $remainder, $prefix);
while ( defined($remainder = <DATA>) ){
while ( Get_paren_text() ){
$prefix =~ s/ //g;
print $prefix, $extracted, "\n";
}
}
sub Get_paren_text {
($extracted, $remainder, $prefix)
= extract_bracketed($remainder, '()', '[\w ]+');
return defined $extracted;
}
__DATA__
CHANNEL(TO.IPTWX01) CHLTYPE(CLUSRCVR) FOO( ( BAR) )
DISCINT(6000) SHORTRTY(10) BIZZ((((BUZZ) ) ) ) )
TRPTYPE(TCP) DESCR( )
LONGTMR(1200) SCYEXIT( )
CONNAME(NODE(1414)) MREXIT( )
MREXIT( ) CONNAME2(SOME(1416))
TPNAME( ) BATCHSZ(50)
MCANAME( ) MODENAME( )
ALTTIME(00.41.56) SSLPEER()
CONTRIVED() ATTR (00-41-56)
CONTRIVED() DOCTORED()
MSGEXIT( )
source to share
I wanted to try and use Regexp::Grammars
.
So here it is:
#! /opt/perl/bin/perl
use strict;
#use warnings;
use 5.10.1;
use Regexp::Grammars;
my $grammar = qr{
<line>
<token: line>
(?: <[pair]> \s* )+
(?{
my $arr = $MATCH{pair};
local $MATCH = {};
for my $pair( @$arr ){
my($key) = keys %$pair;
my($value) = values %$pair;
$MATCH->{$key} = $value;
}
})
<token: pair>
<attrib> \s* \( \s* <value> \s* \)
(?{
$MATCH = {
$MATCH{attrib} => $MATCH{value}
};
})
<token: attrib>
[^()]*?
<token: value>
(?:
<MATCH=pair> |
[^()]*?
)
}x;
use warnings;
my %attr;
while( my $line = <> ){
$line =~ /$grammar/;
for my $key ( keys %{ $/{line} } ){
$attr{$key} = $/{line}{$key};
}
}
use YAML;
say Dump \%attr;
--- ALTTIME: 00.41.56 ATTR: 00-41-56 BATCHSZ: 50 CHANNEL: TO.IPTWX01 CHLTYPE: CLUSRCVR CONNAME: NODE: 1414 CONNAME2: SOME: 1416 CONTRIVED: '' DESCR: '' DISCINT: 6000 DOCTORED: '' LONGTMR: 1200 MCANAME: '' MODENAME: '' MREXIT: '' MSGEXIT: '' SCYEXIT: '' SHORTRTY: 10 SSLPEER: '' TPNAME: '' TRPTYPE: TCP
source to share
while ( <IN2> ) {
while ( /([A-Z]+)\s*(\((?:[^()]*+|(?2))*\))/g ) {
print "$1$2\n";
}
}
This works for nested partners like
CONNAME(NODE(1414, SOME(1416) ) ) ATTR (00-41-56)
The (? 2) part is recursive, * + means "don't back out" - only works in Perl 5.10 or later; I got this from http://faq.perl.org/perlfaq6.html#Can_I_use_Perl_regul
source to share
#!/usr/bin/perl
use strict;
use warnings;
my @parsed;
while ( my $line = <DATA> ) {
while ( $line =~ / ([A-Z0-9]+) \s* \( (.*?) \) \s /gx ) {
push @parsed, { $1 => $2 }
}
}
use Data::Dumper;
print Dumper \@parsed;
__DATA__
CHANNEL(TO.IPTWX01) CHLTYPE(CLUSRCVR)
DISCINT(6000) SHORTRTY(10)
TRPTYPE(TCP) DESCR( )
LONGTMR(1200) SCYEXIT( )
CONNAME(NODE(1414)) MREXIT( )
MREXIT( ) CONNAME2(SOME(1416))
TPNAME( ) BATCHSZ(50)
MCANAME( ) MODENAME( )
ALTTIME(00.41.56) SSLPEER()
CONTRIVED() ATTR (00-41-56)
CONTRIVED() DOCTORED()
MSGEXIT( )
source to share