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

Dear Gods,...Hello

*I have been trying all day to no avail,..PLEASE HELP*

I have data like this
-------------------- show port-channel database ------------------ port-channel 1 Administrative channel mode is on Operational channel mode is on Last membership update succeeded First operational port is fc1/5 2 ports in total, 2 ports up Ports: fc2/5 [up] fc1/5 [up] * port-channel 3 Administrative channel mode is on Operational channel mode is on Last membership update succeeded First operational port is fc1/1 1 port in total, 1 port up Ports: fc1/1 [up] * ******************************************************************
All I am trying to do is to create a data structure (i.e a Hash) where I can populate it like so;
$Rec->{Port_Channel_Name} = "port-channel 1" $Rec->{port_member} = fc2/5 $Rec->{port_member} = fc1/5 . . . $Rec->{port_member} = fcX/X $Rec->{Port_Channel_Name} = "port-channel 3" $Rec->{port_member} = fc1/1
I have tried all day with so many different variations of the code below and still getting nowhere very fast!
for my $Info (@Switch_Array) { if ($Info =~/show port-channel database/) { next unless ($Info ne ""); my @Filter = (split /(port-channel \d\n)/,$Info); print "$1 : $Filter[0]\n"; # for my $Filtered (@Filter) # { # print "$Filtered \n"; } }
Maybe a second fresh pair of eyes can spot where and why I am going wrong!!

Thanks a million again.
Blackadder

Replies are listed 'Best First'.
Re: Splitting data into chunks!
by ikegami (Patriarch) on Sep 20, 2005 at 18:27 UTC

    The key problem lies in your lack of understanding in what data structure is required. In your design example, you assigned 6 values to 2 variables. That doesn't add up.

    You could have an array of port channel records:

    $port_channels[0]{ name => "port-channel 1" , members => [ 'fc2/5' , 'fc1/5' ] }; $port_channels[1]{ name => "port-channel 3" , members => [ 'fc1/1' ] };

    But why not just index by the port-channel:

    $port_channels{1}{ members => [ 'fc2/5' , 'fc1/5' ] }; $port_channels{3}{ members => [ 'fc1/1' ] };

    Since your record only has one field, why not forget the record and just have an array:

    $port_channels{1}[ 'fc2/5' , 'fc1/5' ]; $port_channels{3}[ 'fc1/1' ];

    The following implements the last one. It creates a hash of port channels, where a port channel is an array of ports.

    use strict; use warnings; my %port_channels; { my $channel; my $in_ports = 0; while (<DATA>) { if (/^port-channel\s+(\d+)/) { $channel = $1; $port_channels{$channel} = []; $in_ports = 0; next; } next unless defined $channel; if (/^\s*Ports:\s+(\S+)\s+\[/) { $in_ports = 1; push(@{$port_channels{$channel}}, $1); next; } if ($in_ports) { if (/\s+(\S+)\s+\[/) { push(@{$port_channels{$channel}}, $1); next; } $in_ports = 0; } } } require Data::Dumper; print(Data::Dumper::Dumper(\%port_channels)); __DATA__ port-channel 1 Administrative channel mode is on Operational channel mode is on Last membership update succeeded First operational port is fc1/5 2 ports in total, 2 ports up Ports: fc2/5 [up] fc1/5 [up] * port-channel 3 Administrative channel mode is on Operational channel mode is on Last membership update succeeded First operational port is fc1/1 1 port in total, 1 port up Ports: fc1/1 [up] *

    Output:

    $VAR1 = { '1' => [ 'fc2/5', 'fc1/5' ], '3' => [ 'fc1/1' ] };

    Update: Added the explanation at the top.

Re: Splitting data into chunks!
by kwaping (Priest) on Sep 20, 2005 at 18:42 UTC
    Yet another solution...

    Update1: Slightly modified to get rid of the useless "port_member" key.
    Update2: Golfed, just for fun.

    #!/usr/bin/perl use strict; use warnings; use Data::Dumper::Simple; my ($Rec,$pc_name); while (<DATA>) { $pc_name = $1, next if (/(port-channel \d+)$/); $Rec->{$pc_name}->{$1} = $2 if (m|(fc\d+/\d+)\s+\[(\w+)\]|); } print Dumper($Rec); __DATA__ port-channel 1 Administrative channel mode is on Operational channel mode is on Last membership update succeeded First operational port is fc1/5 2 ports in total, 2 ports up Ports: fc2/5 [up] fc1/5 [up] * port-channel 3 Administrative channel mode is on Operational channel mode is on Last membership update succeeded First operational port is fc1/1 1 port in total, 1 port up Ports: fc1/1 [up] *

    Output:
    $Rec = { 'port-channel 3' => { 'fc1/1' => 'up' }, 'port-channel 1' => { 'fc1/5' => 'up', 'fc2/5' => 'up' } };
      Hi

      I ma trying to include this code within my script but it seems to through errors!

      My Code
      #! c:/perl/bin/perl.exe # use strict; use warnings; use Data::Dumper; my ($Rec,$pc_name); undef $/; open (LST,"c:/tim/showtech".$ARGV[0].".txt") || die "$!\n"; my $Switch_Data =<LST>; close LST; my @Switch_Array = split(/\*{66}\n/, $Switch_Data); for (@Switch_Array) { next unless (/show port-channel database/); $pc_name = $1, next if (/(port-channel \d+)$/); $Rec->{$pc_name}->{$1} = $2 if (m|(fc\d+/\d+)\s+\[(\w+)\]|); #print "$_\n"; }
      the error I ma getting is
      Use of uninitialized value in hash element at C:\Perl\Vodafone\pm_data +_chunks2.p l line 20.
      Any Idea why?

      Blackadder
Re: Splitting data into chunks!
by salvix (Pilgrim) on Sep 20, 2005 at 18:41 UTC
    This works and should be easy for you to extend it:
    use strict; use warnings; use Data::Dumper; my $state = { started => 0, current_port_channel => undef, inside_ports_item => 0 }; my $data = { port_channel => {} }; while (<DATA>) { chomp; $state->{started}++ and next if /--- show port-channel database -- +-/; next unless $state->{started}; if (/^port-channel (\d+)/) { $data->{port_channel}->{$1} = {}; $state->{current_port_channel} = $1; next; } if ( $state->{current_port_channel} ) { if (/^\s+Ports:\s+(\S+)\s+\[(.+?)]/) { push @{ $data->{port_channel}->{ $state->{current_port_cha +nnel} }->{port_member} }, $1; # play with $2 ("up", "down", if you want to) $state->{inside_ports_item} = 1; next; } if ( $state->{inside_ports_item} and /^\s+(\S+)\s+\[(.+?)]/ ) +{ push @{ $data->{port_channel}->{ $state->{current_port_cha +nnel} }->{port_member} }, $1; next; } } } ### while (<DATA>) print Dumper $data; __DATA__ -------------------- show port-channel database ------------------ port-channel 1 Administrative channel mode is on Operational channel mode is on Last membership update succeeded First operational port is fc1/5 2 ports in total, 2 ports up Ports: fc2/5 [up] fc1/5 [up] * port-channel 3 Administrative channel mode is on Operational channel mode is on Last membership update succeeded First operational port is fc1/1 1 port in total, 1 port up Ports: fc1/1 [up] * ******************************************************************
    Output:
    $VAR1 = { 'port_channel' => { '1' => { 'port_member' => [ 'fc2/5', 'fc1/5' ] }, '3' => { 'port_member' => [ 'fc1/1' ] } } };
    []s, Sergio
Re: Splitting data into chunks!
by Roy Johnson (Monsignor) on Sep 20, 2005 at 18:40 UTC
    How is your data currently broken up? Your code suggests that everything you showed is one record in @Switch_Array. So you're splitting it on the port-channel lines, which seems ok; you'll get alternating tags and values from that, so you might assign it to a hash. But not if ordering is important.

    So you've got a list of elements, each of which is either a port-channel line or a block of data, from which you want to extract the port list.

    my @Filter = split /(port-channel \d\n)/, $Info; for (@Filter) { unless (/^port/) { # strip out the junk s/.*Ports://s; # Everything up to Ports:, even crossing line b +oundaries s/^\s+//m; # Leading spaces on lines s/\s.*//; # Everything from the first space to the end of + a line } print; }

    Caution: Contents may have been coded under pressure.
Re: Splitting data into chunks!
by sh1tn (Priest) on Sep 20, 2005 at 19:22 UTC
    while(<DATA>){ if(/port-channel\s+(\d+)/ .. /]\s+\*/){ $1 and $section = $1; $section || next; if(/\s+Ports:/ .. /\n\n/){ /(\S+)\s+(\[\S+\])/ and $data->{$section}{$1} = $2 } } } use Data::Dumper; die Dumper($data) __END__ $VAR1 = { '1' => { 'fc2/5' => '[up]', 'fc1/5' => '[up]' }, '3' => { 'fc1/1' => '[up]' } };


      Since this is the shortest code, I used it.

      My question is how do I access the 3rd element (i.e. the UP value)?

      This is what I did;
      use strict; use warnings; use Data::Dumper; my ($Rec,$pc_name); while (<DATA>) { $pc_name = $1, next if (/(port-channel \d+)$/); $Rec->{$pc_name}->{$1} = $2 if (m|(fc\d+/\d+)\s+\[(\w+)\]|); } for my $data (keys %{$Rec}) { print "\n$data \n"; for my $data2 (keys %{$Rec->{$data}}) { print "\t$data2 : \n"; } } __DATA__ port-channel 1 Administrative channel mode is on Operational channel mode is on Last membership update succeeded First operational port is fc1/5 2 ports in total, 2 ports up Ports: fc2/5 [up] fc1/5 [up] * port-channel 3 Administrative channel mode is on Operational channel mode is on Last membership update succeeded First operational port is fc1/1 1 port in total, 1 port up Ports: fc1/1 [up] *
      Output
      C:\Perl\test>pm_data_chunks.pl port-channel 1 : fc2/5 : fc1/5 : port-channel 3 : fc1/1 :
      Blackadder
      ************************UPDATE***********************

      I've added this: print "\t$data2 : $Rec->{$data}->{$data2}\n";

      Sorry for my hasty post...Many Thanks.
        Change the second print like that:
        ... no strict 'refs'; for my $data2 (keys %{$Rec->{$data}}) { print "\t$data2: \t $Rec->{$data}{$data2} \n"; } ...


Re: Splitting data into chunks!
by gargle (Chaplain) on Sep 20, 2005 at 18:51 UTC

    Hi,

    A quick try:

    Note that I check for blank lines as a seperator between 'records'. I don't do anything with the seperator yet, but, just in case... It's there if you need it.

    I also place a next in my if statements, no need to keep on checking a record when the treatment has been done.

    I used a hash in a hash because a) I noticed the UP besides each 'port'. So you might want to replace 'def' with the value UP whenever that UP can be something else and b) it allows you to easily check with the defined keyword for ports that were already attributed for a channel

    #!/usr/bin/perl use strict; use warnings; use Data::Dumper; my $data = {}; my $channel = undef; while (defined (my $record = <DATA>)) { chomp $record; if ( $record =~ /^$/ ) { $channel = undef; # port-channels are seperated by blank lines + next; } # I know I'm not in a record when I look for a port-channel # I could add a die here to signal problems with the format if ( !defined channel && $record =~ /^port-channel (\d+)$/ ) { $channel = $1; # this signals a new channel next; } # I check if I'm in a record and if so I test for fc's if ( $defined $channel && $record =~ /fc(\d+)\/(\d+)\s+\[(.+)\]/ ) + { my $port = "fc$1\/$2"; my $status = $3; die "something wrong" if defined $data->{$channel}->{$port}; $data->{$channel}->{$port} = $status; next; } } print Dumper $data; __DATA__ port-channel 1 Administrative channel mode is on Operational channel mode is on Last membership update succeeded First operational port is fc1/5 2 ports in total, 2 ports up Ports: fc2/5 [up] fc1/5 [up] * port-channel 3 Administrative channel mode is on Operational channel mode is on Last membership update succeeded First operational port is fc1/1 1 port in total, 1 port up Ports: fc1/1 [up] *

    Output:

    $VAR1 = { '1' => { 'fc2/5' => 'up', 'fc1/5' => 'up' }, '3' => { 'fc1/1' => 'up' } };

    ps: this node contains interesting toughts about treating records.

    --
    if ( 1 ) { $postman->ring() for (1..2); }
      WOW.....Thanks guys.

      Viva PerlMonks

      Blackadder