http://www.perlmonks.org?node_id=1208816

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

Hi all, I have a problem I just cannot figure out. I'm reading an input line by line and matching stuff to pass into a key. The input data is as follows

dnsname1: static-route 1.1.1.1/30 next-hop 2.2.2.2 preferen +ce 200 bfd-enable tag 685 dnsname1- service-name "XYZ12345-s12345" dnsname2: static-route 3.3.3.3/30 next-hop 4.4.4.4 bfd-enab +le tag 635 dnsname2- service-name "XYZ67891-s67890"

I initially matched just on the static-route line to print it out with the dns name, but actually it would be good to also print out what service it belongs to, which appears on the line below. How would I match on both lines and pass the service-name to a variable as well that I could feed into the same key? The code is riddled with perltidy errors as I've been playing, but this is how far I got.

if (@grep) { my $hop; my $static; my $tag; my $vpna; my $dns; print GREEN "\nFound some static routes...\n", RESET; foreach my $line (@grep) { if $line ( =~ m/^(\S+):\s+static-route (\S+) next-hop (\S+)( pre +ference \d+|)( bfd-enable|) tag (\d+)/ ){ my ( $dns, $static, $hop, $tag ) = ( $1, $2, $3, $6 ); } if $line ( =~ m/^(\S+)-\s+service-name (\S+)/ ){ my ($vpna) = ( $2 ); } $results{$dns}{$vpna}{$static}{$hop} = $tag; } print Dumper \%results; for my $box ( keys %results ) { print BOLD "\n$box\n", RESET; for my $static ( keys %{ $results{$box} } ) { for my $hop ( keys %{ $results{$box}{$static} } ) { print "$static\t next-hop $hop\t"; print "tag $results{$box}{$static}{$hop}\n"; print "\n"; } } } } else { print "No static routes exist for $ip\n"; } }

Replies are listed 'Best First'.
Re: Store multiple lines
by hippo (Bishop) on Feb 09, 2018 at 12:42 UTC

    This might be what you're after, but it's probably fragile and definitely still horribly messy. I wouldn't start from where you are, in all honesty.

    use strict; use warnings; my @grep = <DATA>; my $ip = 'whatever'; if (@grep) { my $hop; my $static; my $tag; my $vpna; my $dns; my %results; print "\nFound some static routes...\n"; while (my $line = shift @grep) { if ($line =~ m/^(\S+):\s+static-route (\S+) next-hop (\S+)( preference +\d+|)( bfd-enable|) tag (\d+)/ ) { ($dns, $static, $hop, $tag) = ($1, $2, $3, $6); $line = shift @grep; $line =~ m/^(\S+)-\s+service-name (\S+)/; $vpna = $2; $results{$dns}{$vpna}{$static}{$hop} = $tag; } } #print Dumper \%results; for my $box (keys %results) { print "\n$box\n"; for my $service (keys %{$results{$box}}) { for my $static (keys %{$results{$box}{$service}}) { for my $hop (keys %{$results{$box}{$service}{$static}} +) { print "$static\t next-hop $hop\t"; print "tag $results{$box}{$service}{$static}{$hop}, se +rvice $service\n"; print "\n"; } } } } } else { print "No static routes exist for $ip\n"; } __DATA__ dnsname1: static-route 1.1.1.1/30 next-hop 2.2.2.2 preferen +ce 200 bfd-enable tag 685 dnsname1- service-name "XYZ12345-s12345" dnsname2: static-route 3.3.3.3/30 next-hop 4.4.4.4 bfd-enab +le tag 635 dnsname2- service-name "XYZ67891-s67890"

      Thanks for your help! As you can probably tell, I'm learning as I go and certainly appreciating the more than one way to do things in Perl. I think what I'm more interested in is actually the best way to do it. Appreciate you've called out the method is messy, and I have to agree. Could I ask possibly how you would do it please?

        It might be simplified depending on relationships between the data columns. For example, if the data was arranged as a table like this

        dnsname   static-route  next-hop  preference  bfd-enable  tag  service-name 
        dnsname1  1.1.1.1/30    2.2.2.2   200         Y           685  XYZ12345-s12345
        dnsname2  3.3.3.3/30    4.4.4.4               Y           635  XYZ67891-s67890
        

        Would the first 2 (or 3) columns uniquely define a single row in the table ?

        poj

        Good question! It rather depends on the properties of your data and how you want it structured. I guess that each "service-name" is unique? You could then have a much flatter data structure if you just keyed on that. eg:

        • results
          • service name
            • dns
            • static
            • next hop
            • tag

        But maybe that's no use to you depending on how you would want to use the data later on. Once you know what most useful form the data should take you can go about constructing it (and that might even involve some pre-processing).

Re: Store multiple lines
by tybalt89 (Monsignor) on Feb 09, 2018 at 14:59 UTC
    #!/usr/bin/perl # http://perlmonks.org/?node_id=1208816 use strict; use warnings; local $_ = do { local $/; <DATA> }; my %results; # match two lines at a time $results{$1}{$5}{$2}{$3} = $4 while /^(\S+):\s+ static-route\s(\S+)\s next-hop\s(\S+) (?:\spreference\s\d+)? (?:\sbfd-enable)?\s tag\s(\d ++) \n \1-\s* service-name\s"(\S+)"/gmx; for my $box (sort keys %results) { print "\n$box\n"; for my $service (sort keys %{$results{$box}}) { for my $static (sort keys %{$results{$box}{$service}}) { for my $hop (sort keys %{$results{$box}{$service}{$static}}) { print "$static\t next-hop $hop\t", "tag $results{$box}{$service}{$static}{$hop}, service $servi +ce\n"; } } } } __DATA__ dnsname1: static-route 1.1.1.1/30 next-hop 2.2.2.2 preferen +ce 200 bfd-enable tag 685 dnsname1- service-name "XYZ12345-s12345" dnsname2: static-route 3.3.3.3/30 next-hop 4.4.4.4 bfd-enab +le tag 635 dnsname2- service-name "XYZ67891-s67890"

    Outputs:

    dnsname1 1.1.1.1/30 next-hop 2.2.2.2 tag 685, service XYZ12345-s123 +45 dnsname2 3.3.3.3/30 next-hop 4.4.4.4 tag 635, service XYZ67891-s678 +90
Re: Store multiple lines
by johngg (Canon) on Feb 09, 2018 at 14:41 UTC

    If it is safe to assume that the "service-name" line immediately follows the "static-route" line, and the file is not huge, then I might read all of the data into a scalar then remove the \nxxxxxxxx- parts globally so that all the information for each item was on one line. That would remove the need for the logic of two separate regex matches simplifying the data extraction. I would then open a reference to the scalar and read that line by line. Something like:-

    use strict; use warnings; open my $inFH, q{<}, \ <<EOD or die qq{open: < \ HEREDOC: $!\n}; dnsname1: static-route 1.1.1.1/30 next-hop 2.2.2.2 preferen +ce 200 bfd-enable tag 685 dnsname1- service-name "XYZ12345-s12345" dnsname2: static-route 3.3.3.3/30 next-hop 4.4.4.4 bfd-enab +le tag 635 dnsname2- service-name "XYZ67891-s67890" EOD my $data = do { local $/; <$inFH>; }; close $inFH or die qq{close: < \ HEREDOC: $!\n}; $data =~ s{\n\S+-}{}g; open $inFH, q{<}, \ $data or die qq{open: < \ scalar: $!\n}; while (<$inFH> ) { # Do a single data extraction regex here. ... } close $inFH or die qq{close: < \ scalar: $!\n};

    I hope this is helpful.

    Cheers,

    JohnGG

Re: Store multiple lines
by Anonymous Monk on Feb 09, 2018 at 13:59 UTC
    Have a look at the "awk" utility. Notice how they cleanly solve such problems. Then, look at "a2p."
      Just to clarify – the change is easy. When you see a "dns" record, you capture certain things as you do now. But you don't add to "$results" until you see the subsequent type of record. (Special-case if the subsequent record does not exist ... two "dns" records back-to-back. A simple flag-variable set to True in the first case and to False in the second case can alert your first-case handler that the second-case never occurred since the first-case ran.