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

Got a simple issue that I can't work out! I'm reading a file line by line, and matching on lines with specific text. Then i want to iterate over that line and match against a list

But ... i'm just grabbing the first line that matches, iterating over that then exiting. I can't figure out how to return to the next file line and repeat the process !

foreach $confLine (<$in>) { if ($confLine =~ /ip4-address address=\"/) { foreach (<DATA>) { my $ip = $_; if ($confLine =~ /ip4-address address=\"$ip/) { $confLine =~ s/\/>/update\"\/>/; } } } print $out $confLine; } ___DATA___ 1.2.3.4 1.2.3.5

Replies are listed 'Best First'.
Re: Return to original loop
by choroba (Cardinal) on Oct 15, 2015 at 11:52 UTC
    <DATA> exhausts the file handle in the first iteration and returns undef in all the following. Either store its content to an array, or use tell/seek to move back to the beginning of the handle.

    BTW, don't use for with <>, use while. Also, use \Q$ip\E to treat the dots literally.

    chomp(my @data = <DATA>); while (my $confLine = <$in>) { if ($confLine =~ /ip4-address address="/) { # No need to backslash + ". for my $ip (@data) { if ($confLine =~ /ip4-address address="\Q$ip/) { $confLine =~ s{/>}{update"/>}; } } } print {$out} $confLine; }

    Or

    my $data_start = tell DATA; while (my $confLine = <$in>) { if ($confLine =~ /ip4-address address="/) { # No need to backslash + ". seek DATA, $data_start, 0; while (my $ip = <DATA>) { chomp $ip; if ($confLine =~ /ip4-address address="\Q$ip/) { $confLine =~ s{/>}{update"/>}; } } } print {$out} $confLine; }

    Update: quotemeta added.

    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

      Thanks, I put the DATA contents into an array and it worked perfectly.

      One question on the quotemeta bit. The code snippet included:

       if ($confLine =~ /ip4-address address="\Q$ip/) {

      Should that be

       if ($confLine =~ /ip4-address address="\Q$ip\E/) {

      Or does it not matter, as the variable was the last thing in the regex? i had to tweak my regex and add a quote at the end, as it was matching incorrectly (i.e. 1.2.3.1 would also match 1.2.3.11, .12, etc.), so have gone with:

       if ($confLine =~ /ip4-address address="\Q$ip\E"/) {

        Hello stroke,

        See the section “Escape sequences” in perlre#Regular-Expressions, where \Q is defined as follows:

        \Q          quote (disable) pattern metacharacters until \E

        So you need the \E only when what follows contains metacharacters which are meant to behave as metacharacters. A double quote character " is not a metacharacter, so in this case adding the \E makes no difference.

        Nevertheless, it’s probably good practice to add it, to make the regex clearer and to guard against bugs in the event that the regex is extended in the future.

        See also quotemeta and perlrebackslash.

        Hope that helps,

        Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Re: Return to original loop
by AppleFritter (Vicar) on Oct 15, 2015 at 11:49 UTC

    Your problem is that you're trying to read from DATA again for each line. But after the outer loop's first iteration, DATA is exhausted and won't return anything anymore, so the inner loop's body will not execute anymore.

    You can reset the filehandle using seek to start reading from the beginning again - disclaimer, I've never tried that on DATA -, or (better) you could read from DATA into an array and iterate over that in your inner loop instead.

    my @IPs = <DATA>; foreach $confLine (<$in>) { if ($confLine =~ /ip4-address address=\"/) { foreach (@IPs) { my $ip = $_; if ($confLine =~ /ip4-address address=\"$ip/) { $confLine =~ s/\/>/update\"\/>/; } } } print $out $confLine; } ___DATA___ 1.2.3.4 1.2.3.5

    Also, just for general reference: you can stick labels in front of loops and pass a label to next to have it start the next iteration of a specific loop, e.g.:

    INPUTLINE: foreach my $line (<<>>) { chomp $line; foreach my $pattern (@patterns) { $line =~ $pattern and do { say "$line matched $pattern!"; next INPUTLINE; } } }

    This also works for last and redo.

Re: Return to original loop
by NetWallah (Canon) on Oct 15, 2015 at 18:41 UTC
    If you have < ~ 2M IP's to check, you could use this (untested):
    my %ips_to_check = map {chomp; $_ => undef} <DATA>; while (my $confLine = <$in>) { if ($confLine =~ /ip4-address address="([\d\.]+)"/ and exists $ips_to_check{$1}){ $confLine =~ s{/>}{update"/>}; } print {$out} $confLine; }

            Software efficiency halves every 18 months, thus compensating for Moore's Law.