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

To fully illustrate the input, I'll include some Unix `w' (who) output from a Concord agent:

13 1.3.6.1.4.1.546.1.1.5.1.1.1 INTEGER 1
13 1.3.6.1.4.1.546.1.1.5.1.1.2 INTEGER 2
13 1.3.6.1.4.1.546.1.1.5.1.1.3 INTEGER 3
13 1.3.6.1.4.1.546.1.1.5.1.1.4 INTEGER 4
13 1.3.6.1.4.1.546.1.1.5.1.2.1 OctetString user
13 1.3.6.1.4.1.546.1.1.5.1.2.2 OctetString user
13 1.3.6.1.4.1.546.1.1.5.1.2.3 OctetString user
13 1.3.6.1.4.1.546.1.1.5.1.2.4 OctetString user
13 1.3.6.1.4.1.546.1.1.5.1.3.1 OctetString pts/2
13 1.3.6.1.4.1.546.1.1.5.1.3.2 OctetString pts/3
13 1.3.6.1.4.1.546.1.1.5.1.3.3 OctetString pts/4
13 1.3.6.1.4.1.546.1.1.5.1.3.4 OctetString pts/5
13 1.3.6.1.4.1.546.1.1.5.1.4.1 INTEGER 10096
13 1.3.6.1.4.1.546.1.1.5.1.4.2 INTEGER 11928
13 1.3.6.1.4.1.546.1.1.5.1.4.3 INTEGER 4
13 1.3.6.1.4.1.546.1.1.5.1.4.4 INTEGER 21612
13 1.3.6.1.4.1.546.1.1.5.1.5.1 TimeTicks 11d 07:39:59
13 1.3.6.1.4.1.546.1.1.5.1.5.2 TimeTicks 9d 05:47:57
13 1.3.6.1.4.1.546.1.1.5.1.5.3 TimeTicks 14d 03:29:03
13 1.3.6.1.4.1.546.1.1.5.1.5.4 TimeTicks 0d 00:29:52
13 1.3.6.1.4.1.546.1.1.5.1.6.1 OctetString machine.domain.com
13 1.3.6.1.4.1.546.1.1.5.1.6.2 OctetString machine.domain.com
13 1.3.6.1.4.1.546.1.1.5.1.6.3 OctetString machine.domain.com
13 1.3.6.1.4.1.546.1.1.5.1.6.4 OctetString machine.domain.com
12 1.3.6.1.4.1.546.1.1.6.1.0 OctetString 00

I'd like to transform that into lines that mimic traditional w/who output as such

user1 pts/2 10096 11d 07:39:59 machine.domain.com

I'm a bit disheveled as to the most appropriate data structures to use. My only hunch is some iterative way to store the vars into dynamically named subarrays based upon the 1st column's count, then print them w/a ref'd loop or something. Beyond that, I'd like to be able to have any number of columns as input and output them similarly.

Replies are listed 'Best First'.
Re: Parsing SNMP output
by Aristotle (Chancellor) on Mar 20, 2004 at 06:37 UTC

    dynamically named subarrays based upon the 1st column's count

    Yes, you want a hash of arrays.
    my %snmp_who; while(<>) { my ( $id, $type, $value ) = split " ", $_, 3; # !FIXME! push @{ $snmp_who{$id} }, $value; } print "@{ $snmp_who{$_} }\n" for keys %snmp_who;

    You should at least read perldoc perlreftut. perldoc perldsc and perldoc perllol also provide helpful introductions to the same topics.

    Note that I don't know the exact specification of the SNMP reply format, so my split solution is likely broken.

    Come to think of it there's Net::SNMP on CPAN, so unless you have a really good reason not to (and there are very very few of those), you should use that instead.

    Makeshifts last the longest.

Re: Parsing SNMP output
by matija (Priest) on Mar 20, 2004 at 06:54 UTC
    I think your best bet is to obtain the MIB definition for this SNMP data, then use SNMP::MIB::Compiler to convert the numbers to symbolic names as you get them. You can then use each symbolic name as the hash key, and simply push all the results with the same key, giving you a hash of arrays.

    Of course, if you want to output it like who, an array of hashes would be more convenient. The code for that would be something like

    use SNMP::MIB::Compiler; my $mib = new SNMP::MIB::Compiler; while (my ($oid,$type,$val)=read_snmp) { $oid=s/\.(\d+)$//; $index=$1; $arr[$index]{$mib->convert_oid($oid)}=$val; }
    Giving you an array of hashes quite suitable for simple printing a-la who.
Re: Parsing SNMP output
by bageler (Hermit) on Mar 20, 2004 at 17:30 UTC
    I would build a hash of hashes. hash of hashes of hashes if you're gathering more than one segment of the snmp data. of course, CPAN is always a first choice but this would work in a pinch.
    #!/usr/bin/perl use strict; my %users; while (<DATA>) { $users{$1}{$2}=$3 if /1\.1\.5\.1\.\d\.(\d)\ (\w+)\ (\w+)/; } use Data::Dumper; print Dumper(%users); __DATA__ 13 1.3.6.1.4.1.546.1.1.5.1.1.1 INTEGER 1 13 1.3.6.1.4.1.546.1.1.5.1.1.2 INTEGER 2 13 1.3.6.1.4.1.546.1.1.5.1.1.3 INTEGER 3 13 1.3.6.1.4.1.546.1.1.5.1.1.4 INTEGER 4 13 1.3.6.1.4.1.546.1.1.5.1.2.1 OctetString user 13 1.3.6.1.4.1.546.1.1.5.1.2.2 OctetString user 13 1.3.6.1.4.1.546.1.1.5.1.2.3 OctetString user 13 1.3.6.1.4.1.546.1.1.5.1.2.4 OctetString user 13 1.3.6.1.4.1.546.1.1.5.1.3.1 OctetString pts/2 13 1.3.6.1.4.1.546.1.1.5.1.3.2 OctetString pts/3 13 1.3.6.1.4.1.546.1.1.5.1.3.3 OctetString pts/4 13 1.3.6.1.4.1.546.1.1.5.1.3.4 OctetString pts/5 13 1.3.6.1.4.1.546.1.1.5.1.4.1 INTEGER 10096 13 1.3.6.1.4.1.546.1.1.5.1.4.2 INTEGER 11928 13 1.3.6.1.4.1.546.1.1.5.1.4.3 INTEGER 4 13 1.3.6.1.4.1.546.1.1.5.1.4.4 INTEGER 21612 13 1.3.6.1.4.1.546.1.1.5.1.5.1 TimeTicks 11d 07:39:59 13 1.3.6.1.4.1.546.1.1.5.1.5.2 TimeTicks 9d 05:47:57 13 1.3.6.1.4.1.546.1.1.5.1.5.3 TimeTicks 14d 03:29:03 13 1.3.6.1.4.1.546.1.1.5.1.5.4 TimeTicks 0d 00:29:52 13 1.3.6.1.4.1.546.1.1.5.1.6.1 OctetString machine.domain.com 13 1.3.6.1.4.1.546.1.1.5.1.6.2 OctetString machine.domain.com 13 1.3.6.1.4.1.546.1.1.5.1.6.3 OctetString machine.domain.com 13 1.3.6.1.4.1.546.1.1.5.1.6.4 OctetString machine.domain.com
Re: Parsing SNMP output
by Anonymous Monk on Mar 21, 2004 at 04:30 UTC
    #!/usr/bin/perl use strict; use warnings; my $base = '1.3.6.1.4.1.546.1.1.5.1'; my %keep = ( '1' => 'ndx', '2' => 'user', '3' => 'pts', '4' => 'huh?', '5' => 'time', '6' => 'host', ); my %kept; while ( <DATA> ) { my ( $oid, $ndx, $val ) = /^ 13 \s $base \. (\d) \. (\d) \s \w+ \s ( +.*) $/x or next; push @{ $kept{$oid} }, $val if ( exists $keep{$oid} ); } while ( shift @{ $kept{1} } ) { my ( @info ) = map { shift @{ $kept{$_} } } 2 .. 6; print "@info", $/; } __DATA__ 13 1.3.6.1.4.1.546.1.1.5.1.1.1 INTEGER 1 13 1.3.6.1.4.1.546.1.1.5.1.1.2 INTEGER 2 13 1.3.6.1.4.1.546.1.1.5.1.1.3 INTEGER 3 13 1.3.6.1.4.1.546.1.1.5.1.1.4 INTEGER 4 13 1.3.6.1.4.1.546.1.1.5.1.2.1 OctetString user 13 1.3.6.1.4.1.546.1.1.5.1.2.2 OctetString user 13 1.3.6.1.4.1.546.1.1.5.1.2.3 OctetString user 13 1.3.6.1.4.1.546.1.1.5.1.2.4 OctetString user 13 1.3.6.1.4.1.546.1.1.5.1.3.1 OctetString pts/2 13 1.3.6.1.4.1.546.1.1.5.1.3.2 OctetString pts/3 13 1.3.6.1.4.1.546.1.1.5.1.3.3 OctetString pts/4 13 1.3.6.1.4.1.546.1.1.5.1.3.4 OctetString pts/5 13 1.3.6.1.4.1.546.1.1.5.1.4.1 INTEGER 10096 13 1.3.6.1.4.1.546.1.1.5.1.4.2 INTEGER 11928 13 1.3.6.1.4.1.546.1.1.5.1.4.3 INTEGER 4 13 1.3.6.1.4.1.546.1.1.5.1.4.4 INTEGER 21612 13 1.3.6.1.4.1.546.1.1.5.1.5.1 TimeTicks 11d 07:39:59 13 1.3.6.1.4.1.546.1.1.5.1.5.2 TimeTicks 9d 05:47:57 13 1.3.6.1.4.1.546.1.1.5.1.5.3 TimeTicks 14d 03:29:03 13 1.3.6.1.4.1.546.1.1.5.1.5.4 TimeTicks 0d 00:29:52 13 1.3.6.1.4.1.546.1.1.5.1.6.1 OctetString machine.domain.com 13 1.3.6.1.4.1.546.1.1.5.1.6.2 OctetString machine.domain.com 13 1.3.6.1.4.1.546.1.1.5.1.6.3 OctetString machine.domain.com 13 1.3.6.1.4.1.546.1.1.5.1.6.4 OctetString machine.domain.com 12 1.3.6.1.4.1.546.1.1.6.1.0 OctetString 00

    user pts/2 10096 11d 07:39:59 machine.domain.com user pts/3 11928 9d 05:47:57 machine.domain.com user pts/4 4 14d 03:29:03 machine.domain.com user pts/5 21612 0d 00:29:52 machine.domain.com