vinoth.ree has asked for the wisdom of the Perl Monks concerning the following question:

use Data::Dumper; my @files = ("ree", "ree1", "ree2"); print "Files: " . join(" ", @files) . "\n"; print Dumper \@files; foreach(@files) { print "The file is $_\n"; func(); } sub func { open(READ, "< test.txt"); while(<READ>) { # print; } close READ; return; } print "Files: " . join(" ", @files) . "\n";

The above produces the
Files: ree ree1 ree2
The file is ree
The file is ree1
The file is ree2
Files:

But I expected the result should be
Files: ree ree1 ree2
The file is ree
The file is ree1
The file is ree2
Files: ree ree1 ree2

Vinoth,G

Replies are listed 'Best First'.
Re: Array element getting deleted
by Corion (Patriarch) on Apr 17, 2009 at 11:48 UTC

    See perlsyn about Foreach-Loops. Basically, $_ acts like an alias and the reading from a file overwrites it.

    Also, you should learn to check the success of open and to emit error messages. Also, Data::Dumper::Dumper takes an array reference not an array.

      Hmmm ,

      ...Data::Dumper::Dumper takes an array reference not an array - not quite true, methinx. It's certainly true that the output is more readable if given a ref. - consider

      pointo1d@pointo1d-laptop:~/workspace/perl$ perl -MData::Dumper -e '@a += qw/0 1 2 3/;warn Dumper \@a,@a' $VAR1 = [ '0', '1', '2', '3' ]; $VAR2 = '0'; $VAR3 = '1'; $VAR4 = '2'; $VAR5 = '3';
      A user level that continues to overstate my experience :-))
Re: Array element getting deleted
by targetsmart (Curate) on Apr 17, 2009 at 11:49 UTC
    The variable $_ is getting modified inside while
    try with
    while(my $line = <READ>) {
    otherwise, when the file EOF is reached $_ will be set to undef inside func().
    try dumping after the foreach loop print Dumper \@files; it will output $VAR1 = [ undef, undef, undef ];
    UPDATE
    Unless you are writing a one liner for some quick task, It is always good to declare and use a local variable for the foreach, for, while, etc. It makes the code understandable and it avoids confusion.

    Vivek
    -- In accordance with the prarabdha of each, the One whose function it is to ordain makes each to act. What will not happen will never happen, whatever effort one may put forth. And what will happen will not fail to happen, however much one may seek to prevent it. This is certain. The part of wisdom therefore is to stay quiet.
      Indeed $_ is being modified. Instead of this:
      foreach(@files) {
          print "The file is $_\n";
          func();
      }
      use:
      foreach my $q (@files) {
          print "The file is $q\n";
          func();
      }
      That way the variable is assigned to $q instead of $_ and you can do with it whatever you like.
Re: Array element getting deleted
by ELISHEVA (Prior) on Apr 17, 2009 at 14:16 UTC

    You aren't the first to be bitten by this and you won't be the last. This is enough of a "gotcha" that it gets own discussion in perlvar. Because $_ gets assigned the current value foreach loops and functions like grep and map, it is easy to think that each code block gets its own value of $_.

    However, $_ is a global variable and it only gets its own value inside a code block if you localize it. The for/foreach loop and functions like grep and map do this implicitly. Any subroutine you write has to do this explicitly, like this:

    sub func { local $_; # THIS IS THE KEY LINE YOU NEEDED open(READ, "< test.txt"); while(<READ>) { # print; } close READ; return; } # outputs the final line w/o a problem Files: ree ree1 ree2 $VAR1 = [ 'ree', 'ree1', 'ree2' ]; The file is ree The file is ree1 The file is ree2 Files: ree ree1 ree2

    The behavior of $_ can be confusing, but one *nice* thing about it is that you can use it to create your very own special funky map and grep routines. Here is an example of a small routine that lets you do custom processing on every other list member:

    use strict; use warnings; sub forEveryOther(&@) { my @aData = @_[1..$#_]; my @aResults; local $_; for (my $i=0; $i < $#aData; $i+=2) { $_ = $aData[$i]; push @aResults, $_[0]->(); } return @aResults; }

    which we can use like this

    my @people = (bob => 53, peter => 200); my @hello = forEveryOther { "Hello, $_\n" } @people; print @hello; # outputs Hello, bob Hello, peter

    Best, beth

      ELISHEVA (and other repliers) are offering great advice and council. In addition to the variety of suggestions offered, they also point out one real root, from my perspective, of the problem the OP is facing...i.e., $_ is a global variable. So the subroutine ends up overwriting it.

      This is a clasic example of where the Perl gurus advise that it is important (and the only way for special variables like $_) to use the 'local' function to keep from overwriting it in the subroutine.

      ack Albuquerque, NM
Re: Array element getting deleted
by dHarry (Abbot) on Apr 17, 2009 at 11:48 UTC

    Sometimes/often the use strict/use warnings stuff actually helps you in understanding what goes wrong. Hint:

    readline() on closed filehandle READ
    Use of uninitialized value in join or string

      /methinks your messages reflect the absence of a file named "text.txt" rather than anything that would be resolved by use of strict or warnings. Adding those and running perl -c pl_test/758183.pl returns syntax OK.

      OTOH, checking the open:

      open(READ, "< test.txt") || die "Cannot open text.txt. $!";

      produces this:

      Cannot open text.txt. No such file or directory at pl_test/758183.pl line 18.
Re: Array element getting deleted
by AnomalousMonk (Archbishop) on Apr 17, 2009 at 19:16 UTC
    Just to be painfully clear,  for(each) loops (and map and grep also) implicitly localize the  $_ default scalar;  while loops do not.

    Consider the behavior when  $_ is not protected from the side-effects of the  while loop:

    >perl -wMstrict -MData::Dumper -le "my @array = qw(A B C); $_ = 'foo'; print qq{before foreach loop:}; print Dumper $_, \@array; foreach (@array) { print qq{in foreach loop before while loop: \$_ is $_}; while (<DATA>) { print; } print qq{in foreach loop after while loop: \$_ is }, defined() ? $_ : 'UNdefined' ; } print qq{after foreach loop:}; print Dumper $_, \@array; __DATA__ " before foreach loop: $VAR1 = 'foo'; $VAR2 = [ 'A', 'B', 'C' ]; in foreach loop before while loop: $_ is A in foreach loop after while loop: $_ is UNdefined in foreach loop before while loop: $_ is B in foreach loop after while loop: $_ is UNdefined in foreach loop before while loop: $_ is C in foreach loop after while loop: $_ is UNdefined after foreach loop: $VAR1 = 'foo'; $VAR2 = [ undef, undef, undef ];
    Consider the behavior with explicit localization:
    >perl -wMstrict -MData::Dumper -le "my @array = qw(A B C); $_ = 'foo'; print qq{before foreach loop:}; print Dumper $_, \@array; foreach (@array) { print qq{in foreach loop before while loop: \$_ is $_}; { local $_; while (<DATA>) { print; } } print qq{in foreach loop after while loop: \$_ is }, defined() ? $_ : 'UNdefined' ; } print qq{after foreach loop:}; print Dumper $_, \@array; __DATA__ " before foreach loop: $VAR1 = 'foo'; $VAR2 = [ 'A', 'B', 'C' ]; in foreach loop before while loop: $_ is A in foreach loop after while loop: $_ is A in foreach loop before while loop: $_ is B in foreach loop after while loop: $_ is B in foreach loop before while loop: $_ is C in foreach loop after while loop: $_ is C after foreach loop: $VAR1 = 'foo'; $VAR2 = [ 'A', 'B', 'C' ];
    All the usual suspects: Foreach Loops in perlsyn,  $_ (or  $ARG if you use English;) in perlvar, map, grep.
Re: Array element getting deleted
by Anonymous Monk on Apr 22, 2009 at 10:18 UTC