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

I have a data file:

config system interface edit "dmz" set vdom "root" set ip 192.18.254.1 255.255.254.0 set allowaccess ping fgfm set type physical set alias "Guest-Wlan" next edit "wan2" set vdom "test" set ip 10.4.254.198 255.255.255.252 set allowaccess ping https ssh snmp set type physical set alias "To MON ospf int" next edit "wan1" set vdom "root" set ip 1.1.1.1 255.255.255.252 set allowaccess ping https set type physical set alias "To Internet" next edit "modem" set vdom "root" set mode pppoe set allowaccess fgfm set type physical next edit "ssl.root" set vdom "root" set type tunnel next edit "ssl.MyRealm" set vdom "MyRealm" set type tunnel next edit "internal1" set vdom "root" set ip 10.1.16.3 255.255.255.0 set allowaccess ping https ssh snmp fgfm set type physical set alias "To MON Internal " next edit "internal2" set vdom "MyRealm" set dhcp-relay-service enable set dhcp-relay-ip "192.168.1.1" set ip 192.168.2.1 255.255.254.0 set allowaccess ping https ssh snmp fgfm set type physical set alias "To MINE" next edit "VPN-Dial" set vdom "root" set type tunnel set interface "wan1" next end
I want to be able to parse into say, the "Internal1" section, and slurp up the ip. I have written the following code, but cannot get it to match what I need using multiline and push:
#!/usr/bin/perl -w use Switch; my $num_Args=$#ARGV +1; if ($num_Args !=2) { print "\nUsage: Argument 1 is the filename to parse, Argument 2 is t +he Management Ip address\n"; exit; } my $filename = $ARGV[0]; my $ipaddr= $ARGV[1]; open (my $fh, $filename) or die "Could not open file '$filename' $!\n"; while (my $row = <$fh>) { chomp $row; switch ($row){ #print $row; case /config system interface/ { <>; if (/edit \"internal1\"/) { print "Number was 1\n"; $grabbing=1; } elsif ( /^$/ ) { $grabbing = 0; } if ($grabbing ) { push @grabbed, @_; } } } print @grabbed; }
So by the end it would be nice to have a hash table of interfaces and their ip addresses, or even more simply a bunch of variables: 10.1.16.3 $internal1="10.1.16.3" $wan="10.4.254.198" Etc. I want to compare them to the entered string in @argv, and then do some stuff. Any ideas, or is my 'push' idea totally wrong?
"Two Wheels good, Four wheels bad."

Replies are listed 'Best First'.
Re: Grabbing Structured multi line output from a file?
by aaron_baugher (Curate) on Apr 04, 2015 at 01:50 UTC

    When I find myself wanting to deal with structured text in multi-line chunks, I usually find the $/ variable to be helpful ( look for $INPUT_RECORD_SEPARATOR in perlvar). If you can come up with a text string that defines the boundaries between the chunks, you can read them in and deal with them individually. With a small file, you could slurp in the whole thing and split it with a regex, but that runs into trouble with larger files.

    So, something like this will give you your IPs and their interface names as a hash:

    #!/usr/bin/env perl use 5.010; use strict; use warnings; use Data::Printer; my %ints; local $/ = "next\n edit"; # break lines on this instead of newline while(my $chunk = <DATA>){ if( $chunk =~ /"(\w+)".*set ip (\d+\.\d+\.\d+\.\d+) /s ){ $ints{$1} = $2; } } say p %ints; __DATA__ config system interface edit "dmz" set vdom "root" set ip 192.18.254.1 255.255.254.0 set allowaccess ping fgfm set type physical set alias "Guest-Wlan" next edit "wan2" set vdom "test" set ip 10.4.254.198 255.255.255.252 set allowaccess ping https ssh snmp set type physical set alias "To MON ospf int" next edit "wan1" set vdom "root" set ip 1.1.1.1 255.255.255.252 set allowaccess ping https set type physical set alias "To Internet" next edit "modem" set vdom "root" set mode pppoe set allowaccess fgfm set type physical next edit "ssl.root" set vdom "root" set type tunnel next edit "ssl.MyRealm" set vdom "MyRealm" set type tunnel next edit "internal1" set vdom "root" set ip 10.1.16.3 255.255.255.0 set allowaccess ping https ssh snmp fgfm set type physical set alias "To MON Internal " next edit "internal2" set vdom "MyRealm" set dhcp-relay-service enable set dhcp-relay-ip "192.168.1.1" set ip 192.168.2.1 255.255.254.0 set allowaccess ping https ssh snmp fgfm set type physical set alias "To MINE" next edit "VPN-Dial" set vdom "root" set type tunnel set interface "wan1" next end

    Aaron B.
    Available for small or large Perl jobs and *nix system administration; see my home node.

Re: Grabbing Structured multi line output from a file?
by LanX (Saint) on Apr 04, 2015 at 02:22 UTC
    you should check with a /flip/ .. /flop/ condition to find the lines of the section in between two regex-matches.

    See Flipin good, or a total flop? for an explanation for how the range-operator is used for that.

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Je suis Charlie!

Re: Grabbing Structured multi line output from a file?
by hdb (Monsignor) on Apr 04, 2015 at 07:20 UTC

    I prefer parsing the whole file into a Perl structure and then read the information from there. By modifying the second regex you could also only read the ip addresses: /^\s*set\s*(ip)\s*([.0-9]*)/.

    use strict; use warnings; my @data; while(<DATA>){ push @data, { 'edit' => $1 } if /^\s*edit\s*"(.*)"/; $data[-1]{'set'}{$1} = $2 if /^\s*set\s*(\w+)\s*(.*)/; } defined $_->{'set'}{'ip'} and print "$_->{'edit'}: $_->{'set'}{'ip'}\n +" for @data; __DATA__ config system interface edit "dmz" set vdom "root" set ip 192.18.254.1 255.255.254.0 set allowaccess ping fgfm set type physical set alias "Guest-Wlan" next edit "wan2" set vdom "test" set ip 10.4.254.198 255.255.255.252 set allowaccess ping https ssh snmp set type physical set alias "To MON ospf int" next edit "wan1" set vdom "root" set ip 1.1.1.1 255.255.255.252 set allowaccess ping https set type physical set alias "To Internet" next edit "modem" set vdom "root" set mode pppoe set allowaccess fgfm set type physical next edit "ssl.root" set vdom "root" set type tunnel next edit "ssl.MyRealm" set vdom "MyRealm" set type tunnel next edit "internal1" set vdom "root" set ip 10.1.16.3 255.255.255.0 set allowaccess ping https ssh snmp fgfm set type physical set alias "To MON Internal " next edit "internal2" set vdom "MyRealm" set dhcp-relay-service enable set dhcp-relay-ip "192.168.1.1" set ip 192.168.2.1 255.255.254.0 set allowaccess ping https ssh snmp fgfm set type physical set alias "To MINE" next edit "VPN-Dial" set vdom "root" set type tunnel set interface "wan1" next end
Re: Grabbing Structured multi line output from a file?
by Marshall (Canon) on Apr 04, 2015 at 09:39 UTC
    Here is one technique:
    #usr/bin/perl -w use strict; use Data::Dump qw(pp); my %hash; while (my $line = <DATA>) { my ($edit) = ($line =~ m/^\s*edit\s*\"(.*)\"/)[0]; next unless $edit; #find first "edit line" my $ip = get_ip(); #get ip if it exists in this record #else $ip is a null (not found) $hash{$edit}=$ip if $ip; } #returns ip = null if a valid ip is not found in this record #end of record is "next" sub get_ip { while (<DATA>) { my ($ip) = ($_ =~ /^\s*set ip\s*([\d\.]+)/)[0]; return $ip if ($ip or /next/); } } print pp \%hash; =prints { dmz => "192.18.254.1", internal1 => "10.1.16.3", internal2 => "192.168.2.1", wan1 => "1.1.1.1", wan2 => "10.4.254.198", } =cut __DATA__ config system interface edit "dmz" set vdom "root" set ip 192.18.254.1 255.255.254.0 set allowaccess ping fgfm set type physical set alias "Guest-Wlan" next edit "wan2" set vdom "test" set ip 10.4.254.198 255.255.255.252 set allowaccess ping https ssh snmp set type physical set alias "To MON ospf int" next edit "wan1" set vdom "root" set ip 1.1.1.1 255.255.255.252 set allowaccess ping https set type physical set alias "To Internet" next edit "modem" set vdom "root" set mode pppoe set allowaccess fgfm set type physical next edit "ssl.root" set vdom "root" set type tunnel next edit "ssl.MyRealm" set vdom "MyRealm" set type tunnel next edit "internal1" set vdom "root" set ip 10.1.16.3 255.255.255.0 set allowaccess ping https ssh snmp fgfm set type physical set alias "To MON Internal " next edit "internal2" set vdom "MyRealm" set dhcp-relay-service enable set dhcp-relay-ip "192.168.1.1" set ip 192.168.2.1 255.255.254.0 set allowaccess ping https ssh snmp fgfm set type physical set alias "To MINE" next edit "VPN-Dial" set vdom "root" set type tunnel set interface "wan1" next end
Re: Grabbing Structured multi line output from a file?
by Anonymous Monk on Apr 04, 2015 at 07:48 UTC

    Read by chunks.

    #!/usr/bin/perl use strict; use warnings; my %ips; $/ = 'next'; # read by chunks /edit "(\S+)".*set ip (\S+)/s and $ips{$1} = $2 while <DATA>; use Data::Dump qw(pp); pp \%ips; __DATA__ config system interface edit "dmz" set vdom "root" set ip 192.18.254.1 255.255.254.0 set allowaccess ping fgfm set type physical set alias "Guest-Wlan" next edit "wan2" set vdom "test" set ip 10.4.254.198 255.255.255.252 set allowaccess ping https ssh snmp set type physical set alias "To MON ospf int" next edit "wan1" set vdom "root" set ip 1.1.1.1 255.255.255.252 set allowaccess ping https set type physical set alias "To Internet" next edit "modem" set vdom "root" set mode pppoe set allowaccess fgfm set type physical next edit "ssl.root" set vdom "root" set type tunnel next edit "ssl.MyRealm" set vdom "MyRealm" set type tunnel next edit "internal1" set vdom "root" set ip 10.1.16.3 255.255.255.0 set allowaccess ping https ssh snmp fgfm set type physical set alias "To MON Internal " next edit "internal2" set vdom "MyRealm" set dhcp-relay-service enable set dhcp-relay-ip "192.168.1.1" set ip 192.168.2.1 255.255.254.0 set allowaccess ping https ssh snmp fgfm set type physical set alias "To MINE" next edit "VPN-Dial" set vdom "root" set type tunnel set interface "wan1" next end

    which outputs

    { dmz => "192.18.254.1", internal1 => "10.1.16.3", internal2 => "192.168.2.1", wan1 => "1.1.1.1", wan2 => "10.4.254.198", }
      $/ = 'next'; # read by chunks /edit "(\S+)".*set ip (\S+)/s and $ips{$1} = $2 while <DATA>;

      Eeew ... next can occur anywhere in the data

      edit "nextag" set vdom "second after next" set alias "third after next"

        So tweak it if necessary :)

        $/ = " next\n"; # read by chunks
Re: Grabbing Structured multi line output from a file?
by Anonymous Monk on Apr 03, 2015 at 23:13 UTC
      yup, the sameness is the same
      my $FROM_CONFIG = qr{ (?<config> ^ \s* config (?<config_name> [^\r\n]+ ) [\r\n]+ ) | (?<closer> ^ \s* (?:end|next) \s* [\r\n]+ ) | (?<edit> ^ \s* (?i:edit) \s* "(?<edit_quoted> [^"]+ )" \s* [\r\n]+ ) | (?<set> ^ \s* (?i:set) \s+ #~ (?<set_key> \w+ ) (?<type> \w+ ) \s+ #~ "(?<set_val> [^"]+ )" #~ \s* (?<val> [^\r\n]++ ) [\r\n]+ ) | (?<UHOH> . ) }xms; my @stack = {}; while( $raw =~ m{$FROM_CONFIG}g ){ my $freeze = { %+ }; #~ dd( $freeze ); if( $freeze->{config} ){ my $new = {}; $stack[-1]->{ $freeze->{config_name} } = $new; push @stack, $new; }elsif( $freeze->{edit} ){ my $new = {}; $stack[-1]->{ $freeze->{edit_quoted} } = $new; push @stack, $new; }elsif( $freeze->{set} ){ $stack[-1]->{ $freeze->{type} } = $freeze->{val}; }elsif( $freeze->{closer} ){ pop @stack; } } dd( \@stack ); __END__
Re: Grabbing Structured multi line output from a file?
by karlgoethebier (Abbot) on Apr 04, 2015 at 19:15 UTC

    Just for curiosity: this is about FortiWeb, right?

    Regards, Karl

    «The Crux of the Biscuit is the Apostrophe»

      actually its a fortigate 4.0 firewall.

      "Two Wheels good, Four wheels bad."
Re: Grabbing Structured multi line output from a file?
by Anonymous Monk on Apr 04, 2015 at 18:27 UTC

    It's a natural one-liner :)

    #!/usr/bin/perl use strict; use warnings; my %ips = do { local $/; <DATA>} =~ /^ *edit "(.*?)"\n(?: *set .*\n)*? *set ip (\S+)/gm; use Data::Dump qw(pp); pp \%ips; __DATA__ config system interface edit "dmz" set vdom "root" set ip 192.18.254.1 255.255.254.0 set allowaccess ping fgfm set type physical set alias "Guest-Wlan" next edit "wan2" set vdom "test" set ip 10.4.254.198 255.255.255.252 set allowaccess ping https ssh snmp set type physical set alias "To MON ospf int" next edit "wan1" set vdom "root" set ip 1.1.1.1 255.255.255.252 set allowaccess ping https set type physical set alias "To Internet" next edit "modem" set vdom "root" set mode pppoe set allowaccess fgfm set type physical next edit "ssl.root" set vdom "root" set type tunnel next edit "ssl.MyRealm" set vdom "MyRealm" set type tunnel next edit "internal1" set vdom "root" set ip 10.1.16.3 255.255.255.0 set allowaccess ping https ssh snmp fgfm set type physical set alias "To MON Internal " next edit "internal2" set vdom "MyRealm" set dhcp-relay-service enable set dhcp-relay-ip "192.168.1.1" set ip 192.168.2.1 255.255.254.0 set allowaccess ping https ssh snmp fgfm set type physical set alias "To MINE" next edit "VPN-Dial" set vdom "root" set type tunnel set interface "wan1" next end