Wow. I just found a pretty weird behavior on the part of foreach. It looks like Perl doesn't reset the iterator if you exit the loop via last, so that a future entrance into the loop will start you iterating through your array at the same place as you left off, which means that you will quickly fall off the end of your array and start getting $_ equal to undef. Here is a code snippet that demonstrates the problem, from a very silly quickie I wrote to look up CNAMEs on a misconfigured DNS where host(1) was not working properly...
my $domain_file; FILE: foreach (@domain_files) { $domain_file = $_; print "\tLooking in domain file $domain_file\n" if (DEBUG == 2); open DOMAIN_FILE, $domain_file or die "Could not open domain file $domain_file: $!"; while () #! Could use globbing here... { if (m/$aname_regex/) { $aname = $1; print "\tFound A name: $aname in file $domain_file\n" if DEBUG; #There's only one A name for a given host, so #stop checking files... last FILE; #*Trouble! foreach iterator is not reset. } } }
It just so happens that this loop appears inside a subroutine I call a bunch of times. Even between subroutine calls, the iterator for @domain_files is not reset! In short, @domain_files is screwed up. This seems like a pretty serious bug to me. Is this a known shortcoming of Perl? The work-around is to use a for loop with your own iterator:
FILE: for (my $i = 0; $i <= $#domain_files; $i++) { $domain_file = @domain_files[$i]; print "\tLooking in domain file $domain_file\n" if (DEBUG == 2); open DOMAIN_FILE, $domain_file or die "Could not open domain file $domain_file: $!"; while () #! Could use globbing here... { if (m/$aname_regex/) { $aname = $1; print "\tFound A name: $aname in file $domain_file\n" if +DEBUG; last FILE; } } }
Thanks for any comments you can provide on this. It's a weird one...

Replies are listed 'Best First'.
RE: foreach gotcha!
by chromatic (Archbishop) on Sep 22, 2000 at 02:20 UTC
    I'll go out on a limb and put 10XP that it's a scoping issue, not a Perl bug. The following snippet makes me very suspicious:
    my $domain_file; FILE: foreach (@domain_files) { $domain_file = $_;
    You're declaring a lexical variable outside of the loop. You use implicit aliasing to the magic $_ with your foreach statement. Then, you copy the contents of $_ to $domain_file.

    Hmm. What happens when you leave the loop via last? $domain_file is still in scope, so it will still contain whatever it contained before you broke out of the loop.

    I'll go double or nothing that the following will work better for you:

    FILE: foreach my $domain_file (@domain_files) {
It was a $_ scoping issue!
by Anonymous Monk on Sep 22, 2000 at 05:00 UTC
    Ouch. I just got some mail from the San Francisco Perl Mongers list*, to which I cross-posted my dilemma. In a nutshell, the problem--which the poster, David Schweitzer, correctly guessed without seeing the rest of my code!--is that I was calling the subroutine enclosing the snippet from within another foreach loop. I had tacitly assumed that $_ would be localized within a subroutine, but it isn't so, because $_ and other punctuation variables are global. Hence my subroutine was clobbering its caller's idea of $_ and vice versa, leading to a most confusing situation.

    The situation, interestingly enough, is to localize $_ within the called subroutine. I haven't gotten this to work yet, but David pointed me at an article by Mark-Jason Dominus called <cite> Seven Useful Uses of local</cite> that explains the solution. Pretty neat (or pretty horrible in that it's not default behavior--depends on how you look at it). ;)

    *Randal, we miss you! You should come and give another talk sometime when you're in the City on a fourth Tuesday. :)

      *Randal, we miss you! You should come and give another talk sometime when you're in the City on a fourth Tuesday. :)
      Hey, I'll be in your fair city 30 Oct to 3 Nov... maybe we can set something up! (Whomever you are!)

      -- Randal L. Schwartz, Perl hacker

RE: foreach gotcha!
by merlyn (Sage) on Sep 22, 2000 at 01:58 UTC
    This doesn't fit my experience, and I have plenty of experience. Can you reduce it down to a small reproducible test case? I'd bet the rent money on either something else going on (very likely) or a bug in Perl (very unlikely), but it's clearly not the way it should work, in my understanding.

    -- Randal L. Schwartz, Perl hacker

      Well heck, I'd bet the rent money on either "a bug in Perl" or "something else going on". Doesn't it have to come down to one of those two cases?

      require Salt::Grain;

        Why not both? =)

        use Salt::Grain; my $post = new Salt::Grain; $post->dose(LIBERAL);

        --
        $you = new YOU;
        honk() if $you->love(perl)