Link to home
Start Free TrialLog in
Avatar of coorsman
coorsman

asked on

Pattern Matching

Oh perl gods, I need thy help...

I need help with the following pattern match:

    if (@{$$int{'vlan'}} == 903) {

Inside $$int{'vlan'} is a number.  I want to be able to evaluate this number in an if statement and then print what is stored in $$int{'description'}.

I have included the entire script I am working with below for reference and the problem appears to be at the very end.




#!/usr/bin/perl
sub findint (@) {
    my @cfg = @_;
    my @interfaces = ();
    my $line = "";
    my $intinfo = {};

    foreach $line (@cfg) {
        chomp $line;
        if ($line =~ m/^(interface)\s*(\S*)(.*)$/) {
            $intinfo = {};
            push @interfaces, $intinfo;
            $intinfo->{'intname'} = $2;
        }
        if ($line =~ m/^(\s*description)\s*(.*)$/) {
            $intinfo->{'description'} = $2;
        }
        if ($line =~ m/^(\s*switchport access vlan)\s*(.*)$/) {
            $intinfo->{'vlan'} = $2;
        }
    }
    push @interfaces, $intinfo;

    shift @interfaces;
    return @interfaces;
}

die "Usage: $0 configfile\n" unless (@ARGV);

$configfile = $ARGV[0];

open(CFG_FILE, $configfile) or die "Can not open file, $!";
while (<CFG_FILE>) {
    push @org_config, $_;
}
close (CFG_FILE) or die "Can not close file correctly";


@interfaces = findint (@org_config);
foreach $int (@interfaces) {
    if (@{$$int{'vlan'}} == 903) {
        print "$$int{'description'}\n";
    }
}
Avatar of ozo
ozo
Flag of United States of America image

Did you mean to say
if( $$int{'vlan'} == 903 ){
Avatar of Tintin
Tintin

I think your whole approach to the problem is very muddling with clunky data structures.

Could you please describe in plain English as to what your script is supposed to do.  I'm sure we can write a much neater, shorter, cleaner and clearer version for you.
you could also write $int->{vlan}==903, just as you do in the sub findint.

i agree with Tintin that we could probably come up with a neater script.
- You're slurping the whole config file into an array, even though you're looping over it only once. It would be more efficient to parse the file line by line
- You're passing around arrays, while references would be more efficient
- Your regular expressions are capturing unneccessary bits; you'd be better off with only capturing what you need
- I would make the search criterium a command line parameter as well

That being said, I suppose it's kind of neat to store the records as hash refs.
What is the purpose of

    push @interfaces, $intinfo;

    shift @interfaces;
Avatar of coorsman

ASKER

Thank you for your comments.  Please note that I have very little experience with Perl, although I do have a background in C.  I appreciate any help I can get with this.

Ok, my objective is to supply a list of vlan numbers to the script, and have it return the descriptions for the interfaces which belong to those vlans.

Below is an small example of the file I am trying to parse (from a Cisco switch).  I need to be able to supply a list of vlans to the perl script, such as 910 and 911.  I then want the perl script to either return the descriptions for use in a shell script, or execute a command with the descriptions as part of the command's arguments.


interface GigabitEthernet1/2
 description TS-1-7513-PRI
 logging event link-status
 switchport
 switchport access vlan 42
 switchport mode access
 spanning-tree portfast
!
interface GigabitEthernet2/4
 description IDAR LB EXT
 logging event link-status
 switchport
 switchport access vlan 910
 switchport mode access
 no cdp enable
 spanning-tree portfast
!
interface GigabitEthernet2/5
 description IDAR LB INT
 logging event link-status
 switchport
 switchport access vlan 911
 no cdp enable
 spanning-tree portfast


After getting the descriptions, I would execute the following command where $1|$2|$3|$4 are the descriptions to be filtered on:

indexmaker --filter title=~"$1|$2|$3|$4" --bodyopt="background="../../_themes/corporat/corbkgnd.gif"
 bgcolor="#FFFFFF" text="#000000" link="#000000" vlink="#000000" alink="#000000"" --title="ts-1-6513
-pn1" ts-1-6513-pn1.cfg ts-1-6513-pn2.cfg mw-1-6513-pn1.cfg > /var/
www/html/mrtg/test.html


The perl script above was my attempt to take example perl scripts and make this work... which hasn't exactly worked. :-)  Thanks for all the help.  
$vlans=qr/903|910|911/;
$/="!";
while( <> ){
  push @description,/^(?:\s*description)\s*(.*)$/m if/^(\s*switchport access vlan)\s*($vlans)$/m;
}
$descriptions = join"|",map{quotemeta}@description;
system qq(indexmaker --filter title=~"$descriptions" --bodyopt="background="../../_themes/corporat/corbkgnd.gif" bgcolor="#FFFFFF" text="#000000" link="#000000" vlink="#000000" alink="#000000"" --title="ts-1-6513 -pn1" ts-1-6513-pn1.cfg ts-1-6513-pn2.cfg mw-1-6513-pn1.cfg > /var/www/html/mrtg/test.html);


Using ozo's suggestion, I put together the following:

------
#!/usr/bin/perl
die "Usage: $0 configfile\n" unless (@ARGV);
$configfile = $ARGV[0];
open(CFG_FILE, $configfile) or die "Can not open file, $!";


$vlans=qr/903|910|911/;
$/="!";
while( <CFG_FILE> ){
      push @description,/^(?:\s*description)\s*(.*)$/m if/^(\s*switchport access vlan)\s*($vlans)$/m;
    }
$descriptions = join"|",map{quotemeta}@description;
system qq(indexmaker --filter title=~"$descriptions" --bodyopt="background="../../_themes/corporat/corbkgnd.gif" bgcolor="#FFFFFF" text="#000000" link="#0000
00" vlink="#000000" alink="#000000"" --title="VLANS" ts-1-6513-pn1.cfg ts-1-6513-pn2.cfg mw-1-6513-pn1.cfg > /var/www/html/m
rtg/test.html);

print "$vlans\n";
print "$descriptions\n";

close (CFG_FILE) or die "Can not close file correctly";
------

However, when I run this I get an error about the expression supplied for "--filter title=~".  I inserted the above print statements and got the following:

------
ERROR: invalid filter expression title=~
(?-xism:903|910|911)
                                                                 <--- blank line
------

Thanks again...
It looks like /^(\s*switchport access vlan)\s*(903|910|911)$/m did not match,
or /^(?:\s*description)\s*(.*)$/m did not match.
Did you use the same configfile as your example above?
Is there any whitespace at the end of " switchport access vlan 910"?
I was able to get a little further by adding a \s* to match the VLAN:

/^(\s*switchport access vlan)\s*($vlans)\s*$/m

However, I then got as output for $vlans and $descriptions:

(?-xism:903|910|911)
IDAR\ LB\ EXT|IDAR\ LB\ INT

I need the $descriptions field to not have the \ characters in it, just spaces.

Also, when I use my full configuration file (which is much larger and should have 80+ matches) I get the following:

|DMZPRD05\\1\-MAIL\

I think the carriage returns at the ends of the lines may be causing some of the problems... but I'm not sure how to fix them.  Is there a way to use chomp before the data is stored?
ASKER CERTIFIED SOLUTION
Avatar of ozo
ozo
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thank you very much ozo!  The script works great.

Here is the finished product for any who are interested:

#!/usr/bin/perl
#
# filter-by-vlans.pl
#
# This script will look at a switch config (Native IOS only) and return the port descriptions
# for any ports belonging to VLANs in the given VLAN list.
#
# The script then calls MRTG's indexmaker and uses the descriptions as a filter for the
# interfaces in those VLANs.
#

die "Usage: $0 \"vlan1|vlan2|vlan3\" \"Page Description\" \"htmlfile\"\n" unless (@ARGV);

# Configure variables
$configfile = "/root/mrtg/cat-config";
$vlans=qr/$ARGV[0]/;

# Open switch config file
open(CFG_FILE, $configfile) or die "Cannot open file, $!";

# Store description only if switchport access vlan is one in $vlans list
$/="!";
while( <CFG_FILE> ){
      push @description,/^(?:\s*description)\s*(.*?)\s*$/m if/^(\s*switchport access vlan)\s*($vlans)\s*$/m;
    }

# Join descriptions with a | symbol
$descriptions = join"|",@description;

# Call indexmaker and use $descriptions as the filter
system qq(indexmaker --filter title=~"$descriptions" --bodyopt="background="../../_themes/corporat/corbkgnd.gif" bgcolor="#FFFFFF" text="#000000" link="#000000" vlink="#000000" alink="#000000"" --title="$ARGV[1]" /root/mrtg/ts-1-6513-pn1.cfg /root/mrtg/ts-1-6513-pn2.cfg /root/mrtg/mw-1-6513-pn1.cfg > "$ARGV[2]");

# Output the descriptions on the command line
print "Port Descriptions: $descriptions\n";

# Close switch config file
close (CFG_FILE) or die "Cannot close file correctly";