Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Parsing output from a command

by ronix (Novice)
on Mar 24, 2009 at 15:37 UTC ( #752902=perlquestion: print w/replies, xml ) Need Help??

ronix has asked for the wisdom of the Perl Monks concerning the following question:

Perl Priests, Sorry, if this has been covered I could not figure out the right search terms for finding it. I'm looking for the best method of pulling 2 specific strings from the aix command generated data below (lscfg -vl hdisk*). If it was all space delimited I'd have less questions about ways to do this, but since it is a mixture of spaces, periods, and blank lines I'm at a loss.
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
So I would be looking for just the physical volume name and the serial #:
hdisk0 0009E05B hdisk1 0004A0D2
Thanks for any direction you can give and for taking the time to look.

Replies are listed 'Best First'.
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.
Re: Parsing output from a command
by kennethk (Abbot) on Mar 24, 2009 at 15:55 UTC
    Ahh, the power of regular expressions:

    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:
    1. ^(\w+) - Find a line that starts with alphanumeric characters
    2. \s*.*? - Advance past the smallest amount of arbitrary text until:
    3. Serial Number - I encounter the phrase 'Serial Number'
    4. \.* - Advance past an arbitrary number of '.' characters
    5. (\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.

      Another approach would be to join the lines of the result and match against that. For example:

      #!/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

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 $_; } } }

    Cheers - L~R

        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+)/;

        Cheers - L~R

Re: Parsing output from a command
by ww (Archbishop) on Mar 24, 2009 at 16:17 UTC

    TIMTOWTDI:

    #!/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
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']});
Re: Parsing output from a command
by 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.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://752902]
Approved by olus
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others pondering the Monastery: (6)
As of 2022-12-02 16:12 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Notices?