Re: Parsing output from a command
by BrowserUk (Patriarch) on Mar 24, 2009 at 15:57 UTC
|
By setting $/ to '', it enables 'paragraph mode' whereby each read will terminate when two (or more) consecutive newlines are encountered. That allows you to read each multiline record in your output in two chunks. The header line and then the rest:
#! perl -slw
use strict;
$/ = ''; ## Paragraph mode
while( <DATA> ) {
m[^(\S+)] or die "Bad input format";
my $volName = $1;
$_ = <DATA>;
m[Serial\sNumber\.+([^\n]+)] or die "Bad input format";
printf "%8s %s\n", $volName, $1;
}
__DATA__
...
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
| [reply] [d/l] |
Re: Parsing output from a command
by kennethk (Abbot) on Mar 24, 2009 at 15:55 UTC
|
use strict;
use warnings;
my $data;
{
local $/;
$data = <DATA>;
}
my %drives = ();
while ($data =~ /^(\w+)\s*.*?Serial Number\.*(\w*)\s/smg) {
$drives{$1} = $2;
}
while ( my ($key, $value) = each %drives) {
print "$key $value\n";
}
__DATA__
hdisk0 P1/Z1-Aa 16 Bit LVD SCSI Disk Drive (18200 MB)
Manufacturer................IBM
Machine Type and Model......ST318305LC
FRU Number..................09P4437
Serial Number...............0009E05B
Part Number.................09P4436
hdisk1 P1/Z1-Ab 16 Bit LVD SCSI Disk Drive (18200 MB)
Manufacturer................IBM
Machine Type and Model......ST318305LC
FRU Number..................09P4437
Serial Number...............0004A0D2
Part Number.................09P4436
For information on these marvels of modern science, read perlretut. What I've done in the above is:
- ^(\w+) - Find a line that starts with alphanumeric characters
- \s*.*? - Advance past the smallest amount of arbitrary text until:
- Serial Number - I encounter the phrase 'Serial Number'
- \.* - Advance past an arbitrary number of '.' characters
- (\w*)\s - Find a series of alphanumerics followed by whitespace
The parenthesis capture the results into the variables $1 and $2, which are stored in the hash. The modifiers s and m control how new-lines are treated for matching, and the g modifier makes the while loop repeat so long as a match exists. Also note I localized the default record separator $/, so the entire data section is read in as a single string. | [reply] [d/l] [select] |
|
|
#!/usr/bin/perl
use strict;
use warnings;
# Slurp!
my @results = <DATA>;
my $results_string = join '', @results;
# Note: Direct assignment to hash works due
# to exactly two matches. Error checking would
# not be a bad idea, either (what happens if the
# match fails?)
my %matches = $results_string =~ /(hdisk\d+).+?Serial Number\.+(\w+)/g
+s;
for my $device (keys %matches) {
print "$device $matches{$device}\n";
}
__DATA__
hdisk0 P1/Z1-Aa 16 Bit LVD SCSI Disk Drive (18200 MB)
Manufacturer................IBM
Machine Type and Model......ST318305LC
FRU Number..................09P4437
Serial Number...............0009E05B
Part Number.................09P4436
hdisk1 P1/Z1-Ab 16 Bit LVD SCSI Disk Drive (18200 MB)
Manufacturer................IBM
Machine Type and Model......ST318305LC
FRU Number..................09P4437
Serial Number...............0004A0D2
Part Number.................09P4436
Output:
hdisk1 0004A0D2
hdisk0 0009E05B
| [reply] [d/l] [select] |
Re: Parsing output from a command
by Limbic~Region (Chancellor) on Mar 24, 2009 at 16:00 UTC
|
ronix,
Perhaps something like this (untested)?
#!/usr/bin/perl
use strict;
use warnings;
open(my $cfg, '-|', 'lscfg -vl hdisk*') or die "Unable to open pipe to
+ lscfg: $!";
{
local $/ = "\nhdisk";
while (<$fh>) {
chomp;
$_ = $/ . $_ if $. != 1;
my ($disk) = $_ =~ /^(hdisk\w+)/;
my ($serial) = $_ =~ /Serial Number\.+(\w+)/;
if ($disk && $serial) {
print "$disk\t$serial\n";
}
else {
# enhance parser to handle follow situation
die $_;
}
}
}
| [reply] [d/l] |
|
|
| [reply] |
|
|
BrowserUk,
Can you explain how it will screw up?
I have subsequently tested it and it works as I expect it to. I did find that on my AIX, the output of the command isn't exactly the same as described by the OP - specifically there are two spaces before hdisk## but after accounting for that, it works as intended. Update: The modifications I made that may make the difference (as I said, it worked as expected) were as follows:
local $/ = "\n hdisk";
my ($disk) = $_ =~ /\s+(hdisk\w+)/;
| [reply] [d/l] |
|
|
|
|
Re: Parsing output from a command
by ww (Archbishop) on Mar 24, 2009 at 16:17 UTC
|
#!/usr/bin/perl
use strict;
use warnings;
# pl_test/752902.pl
for (<DATA>) {
chomp;
my $data = $_;
if ( $data =~ m/^(hdisk.)/ ) {
print "$1 \t";
}
if ( $data =~ m/\s+(Serial Number)\.+([\d,A-Z]+)/) { # alt char
+class: [0-9,A-Z] and possibly /i
print "$1\t$2\n";
}
}
__DATA__
hdisk0 P1/Z1-Aa 16 Bit LVD SCSI Disk Drive (18200 MB)
Manufacturer................IBM
Machine Type and Model......ST318305LC
FRU Number..................09P4437
Serial Number...............0009E05B
Part Number.................09P4436
hdisk1 P1/Z1-Ab 16 Bit LVD SCSI Disk Drive (18200 MB)
Manufacturer................IBM
Machine Type and Model......ST318305LC
FRU Number..................09P4437
Serial Number...............0004A0D2
Part Number.................09P4436
output
hdisk0 Serial Number 0009E05B
hdisk1 Serial Number 0004A0D2
| [reply] [d/l] [select] |
Re: Parsing output from a command
by codeacrobat (Chaplain) on Mar 24, 2009 at 19:59 UTC
|
how about a golfed one-liner ;-)
lscfg -vl hdisk*|perl -MList::Pairwise=mapp -00e'print mapp{$a=~/^(hdi
+sk\d+)/&&"$1 ",$b=~/Serial Number[.]+(\w+)/m&&"$1\n"}<>'
update: List::Pairwise oneliner
print+qq(\L@{[ref\&@]}@{['@'x7^'!#2/"!4']});
| [reply] [d/l] [select] |
Re: Parsing output from a command
by locked_user sundialsvc4 (Abbot) on Mar 24, 2009 at 17:54 UTC
|
For completeness, let the record show that you can also choose not to use Perl at all, but rather awk.
awk is a more special-purpose tool, not intended to be a general-purpose programming language. But its “purpose” is very much what you are looking for here. (awk is one of the original “inspirations” for Perl and you'll see that its syntax is quite similar.)
An awk program generally consists of one or more declarations that look like this:
/pattern to look for/{ block of code to execute anytime the pattern is found }
Notice that: awk itself is the “main program.”
It does all the work of opening the file, reading the records, separating them into “fields,” and searching for a matching pattern or patterns, which it does from top to bottom in their order of occurrence.
In addition to regular-expression patterns, certain keywords can be used. Any BEGIN block will be executed at the start of the file (for instance to establish the value of RS or record-separator). At the end of the file, the END block, if any, is run.
Within this general framework, any sort of text-file processing task can be very-simply handled, and you may (or, may not...) find it more expedient to do so than with Perl.
If you anticipate that the task might grow beyond what awk is designed to do, then you should probably select Perl from the outset.
But for other tasks, IMHO, awk is “just the ticket.”
In any case, I find it useful to know about this tool in addition to the Perl approaches given here, and I say you should definitely include it in your toolkit.
| |