Jorge Kageyama
Jorge Kageyama

Reputation: 445

while and foreach interaction

I am having an undesired interaction with a foreach loop and a while that I don't quite understand. I have a normal for loop, and then a foreach loop going through an array. I create a string (representing a file name) with both and then open the file and read it.

The code I use is here:

@array=(1,2);
for($y=0;$y<2;$y++)
{
    foreach(@array)
    {
        print "@array\n";
        $name="/Users/jorge/$_\_vs_$y\.txt";
        print "$name\n";
        open(INFILE,"$name") or die "Can't open files!\n";
        while(<INFILE>)
        {
            $line=$_;
        }
    }
}

and the output is:

array: 1 2
/Users/jorge/1_vs_0.txt
array:  2
/Users/jorge/2_vs_0.txt
array:  
/Users/jorge/_vs_1.txt

Seems that somehow the while loop is shortening my array, if I remove the:

while(<INFILE>)

it works as intented, also if I change the foreach too:

foreach $tmp (@array)

and use $tmp instead of $_, it also works as intended, the output looks like this:

array: 1 2
/Users/jorge/1_vs_0.txt
array: 1 2
/Users/jorge/2_vs_0.txt
array: 1 2
/Users/jorge/1_vs_1.txt
array: 1 2
/Users/jorge/2_vs_1.txt
array: 1 2
/Users/jorge/1_vs_2.txt

Upvotes: 0

Views: 184

Answers (2)

Mark Reed
Mark Reed

Reputation: 95385

You're using $_ as the loop control variable on two nested loops. Instead, you should give each loop its own variable.

Not only is the inner loop changing the value of the control variable for the outer loop, but it's also changing the actual array being looped over. That's because the loop control variable in Perl's for/foreach aliases the elements of the array, so when the <INFILE> construct reads a line into $_, it overwrites the current element of @array with that line. When the while loop finishes reading the file, $_ comes out of the last <INFILE> as undefined, which means the most-recently processed element of @array will always be undefined when you get back to the top of the foreach loop.

You should also be declaring your variables with my, using a lexical scalar instead of a bareword as a file handle, and using the the three-argument version of open, so I've made those changes below as well. But the solution to your shrinking-array problem is just the use of an explicit variable instead of the default $_ for at least one of the two loops.

my @array = (1, 2);
for (my $y = 0; $y < 2; $y++)
{
  foreach my $x (@array)   # using $x instead of $_
  {
    print "@array\n";
    my $name = "/Users/jorge/${x}_vs_$y.txt";  
    print "$name\n";
    open my $infile, '<', $name or die "$0: can't open file '$name': $!\n";
    while (my $line = <$infile>)   # using $line instead of $_
    {
        # do something with $line here
    }
  }
}

Upvotes: 3

mpapec
mpapec

Reputation: 50677

Your while loop overwrites content of array as it uses $_ which is aliased to @array elements.

It is best to use lexical (my) variable when reading file using while,

    while (my $line = <INFILE>) {

        # ..
    }

Upvotes: 1

Related Questions