Beefy Boxes and Bandwidth Generously Provided by pair Networks RobOMonk
go ahead... be a heretic
 
PerlMonks  

opening a file destroys nulling entries in a list?!?!

by matpalm (Initiate)
on Aug 07, 2003 at 02:28 UTC ( #281699=perlquestion: print w/ replies, xml ) Need Help??
matpalm has asked for the wisdom of the Perl Monks concerning the following question:

hi, i am pretty new to perl but this one really confuses me...i have taken some code and tried to make it as minimal as possible for the problem i'm getting

consider 'blah.pl'

#!/usr/bin/perl use strict; sub screwed { my ($in) = @_; open(IN, "<$in"); while(<IN>) { print "file $in contains $_"; } close(IN); } my @instances = ("a", "b"); map { print "before $_\n" } @instances; map { screwed "$_"; } @instances; map { print "after $_\n" } @instances;
and data file 'a'
a1 a2
and data file 'b'
b1 b2
running this i get
before a before b file a contains a1 file a contains a2 file b contains b1 file b contains b2 after after
what has happened to the entries of instances? they are there but are empty (null?)

if i remove the while (<IN>) line i get what i expect...

before a before b opening file named a opening file named b after a after b
so why does iterating over the <IN> with a while kill my instances list?!! i don't get it...

was getting this with a suse7 locally built perl5.6 and thinking it was (maybe) a perl bug i rebuily perl5.8.0 locally and am still getting it.. :( mat

Comment on opening a file destroys nulling entries in a list?!?!
Select or Download Code
Re: opening a file destroys nulling entries in a list?!?!
by cfreak (Chaplain) on Aug 07, 2003 at 02:56 UTC

    $_ is a global variable. Your while is setting it to the last record of your filehandle and since your files end with a \n, $_ returns blank (I guess $_ gets set before the test is done to see if it has a value). Since this is Perl there is more than one way to get around this:

    You can localize $_ in your sub like so:

    sub screwed { my ($in) = @_; local $_; # basically a temporary $_ just for this sub open(IN, "<$in"); while(<IN>) { print "file $in contains $_"; } close(IN); }
    Or you can set each line of the file to a variable in the while like so:
    while(defined(my $foo = <IN>)) { print "file $in contains $foo"; }

    Hope that helps

    Lobster Aliens Are attacking the world!
      To elaborate a bit (it took me some experimenting to realize why cfreak is right), inside a map the variable $_ is magic. While each entry in the list is evaluated, $_ is an alias to that entry. Assigning a new value to $_ changes the value of the current list entry. Because you're calling screwed from within map without localizing $_, it's being assigned to each successive line in the file, and then set to undef to indicate end-of-file. When screwed returns, $_ is still set to undef, so all entries in the list are changed to undef.

      You can verify this by changing screwed to:

      sub screwed { $_ = 'hi'; }
      in which case you'll get:
      before a
      before b
      after hi
      after hi
      

      How come while(<IN>){}, but not for (@x) {}? why is while (<IN>) so special?

      I love perl, but this scares the hell out of me.

      --Bob Niederman, http://bob-n.com

        Nevermend. I think I get it - someone else posted that while(<>){} is the same as while($_=<>){} - it does an assignment to $_ and that's what does the damage.

        Somehwere, I think I remember someone (Tom Christiansen, I think) saying to always localize $_ in subroutines. Now I know why.

        --Bob Niederman, http://bob-n.com
Re: opening a file destroys nulling entries in a list?!?!
by BrowserUk (Pope) on Aug 07, 2003 at 03:21 UTC

    I'm not surprised you are confused:) I get the same behaviour with AS 5.8.

    This isn't a bug, and what's more it is a doosy. I'm amazed I didn't recognise this as the normal behaviour of while. this hasn't shown up earlier. Actually I did, after trying to sleep for about 2 hours later. D'oh. It's the heat.

    The problem lies with the while statement. Somehow, the aliasing of the input to $_ is getting mixd up with the first element of @_. This can be seen by printing the contents of the array just before the while and again inside it. Inside the loop, the first element of the array has been overwritten by the input from the diamond operator. At the end of the while loop, this then gets set to null. The original contents have been blown away!!

    A work around is to explicitly localise $_ in the while as shown commented out above.

    I'll raise a perlbug against this.


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller
    If I understand your problem, I can solve it! Of course, the same can be said for you.

      I'll raise a perlbug against this.

      Please don't. It's documented in perlvar. Search for nasty_break().

      wowzers! thanks for the super fast responses, i pop out to lunch and look what happens :)

      i also tried mapping <in>, as in...

      map { print "file $in contains $_"; } <IN>;
      and that works ok.

      mat

      Somehow, the aliasing of the input to $_ is getting mixd up with the first element of @_

      When you do screwed($_) variable $_ gets aliased to $_[0] inside screwed() as subroutine parameters always aliased to elements of @_ array inside the subroutine. So later when while loop modifies $_ it affects $_[0] too.

      Shorter demo:

      $_ = 1; test($_); sub test { $_ = 2; print $_[0], "\n"; } # output of script __END__ 2
      Yet another example why I think while(<>) { ... } considered harmful.

      --
      Ilya Martynov, ilya@iponweb.net
      CTO IPonWEB (UK) Ltd
      Quality Perl Programming and Unix Support UK managed @ offshore prices - http://www.iponweb.net
      Personal website - http://martynov.org

Re: opening a file destroys nulling entries in a list?!?!
by l2kashe (Deacon) on Aug 07, 2003 at 03:39 UTC

    and now for something entirely different..

    just as an aside, using map in a void context (i.e. not returning anything), is generally considered a BadThing(TM). If you are simply looking for a way to do simple single line loops, you can always use for().. ala

    print "before: $_\n" for @instances; screwed "$_" for @instances; print "after: $_\n" for @instances;

    You might already know this, you might not, but I figured I would toss it out there. The reason it't not so good, is Perl can go through a lot of trouble to build a list of things to return from the map block, and if you don't use them its simply a waste of processor time and memory that could be better spent doing other things.

    use perl;

      my first attempt was with
      foreach(@instances) { screwed "$_"; };
      and then when it didn't work i showed it to someone here at work and he introduced me to maps so i used
      map { screwed "$_"; } @instances;
      but...
      screwed "$_" for @instances;
      ..is even less typing again!
Re: opening a file destroys nulling entries in a list?!?!
by Albannach (Prior) on Aug 07, 2003 at 03:42 UTC
    Well I was about to post a clarification of cfreak's node, but sgifford beat me to it, so I'll just add that map returns a list, so to avoid making perl do all that work for nothing (and not bothering to put a variable in quotes where no interpolation is needed), I'd use:
    print "before $_\n" for @instances; screwed $_ for @instances; print "after $_\n" for @instances;
    Of course for also aliases array elements to $_, so this will still suffer from the same thing that caught you unless you use the local $_ in your sub. As a matter of (possibly paranoid) habit I tend to avoid the various implicit uses of $_ in code that I expect to keep any length of time. While it's extremely handy for one-liners, it's just not worth the potential trouble in more complex code.

    --
    I'd like to be able to assign to an luser

Re: opening a file destroys nulling entries in a list?!?!
by leriksen (Curate) on Aug 07, 2003 at 03:52 UTC
    I am getting slightly different behaviours depending on whether I use
    screwed $_;
    or
    screwed "$_";
    I use AS 5.6.1
    In the first, screwed()::@_ gets clobbered, in the second screwed()::@_ is OK, but @instances is still clobbered.

    And if I could just add, to the original poster, that
    map {function($_)} @array;
    is generally frowned upon - map returns a list, and by not having map on the RHS of an assignment
    @result_set = map {function($_)} @array
    , that list is considered being used in a void context. This means, in this case, all the hard work of creating a list of results via the map is wasted, the list is thrown away. Because screwed() doesn't explicitly return a value, it defaults to using the return value of the close() statement at the end of the sub. If you really dont care about the return values of applying a function using the elements of an array as parameters, it is 'better'(tm) to use this idiom
    foreach (@array) {sub_without_meaningful_return($_)}
    This makes it clear that the function has side-effects - that is, the function uses and possibly changes some variable outside its scope- in screwed()'s case, STDOUT is used.
    merlyn has lectured me once on this - so I now pass this on.
Re: opening a file destroys nulling entries in a list?!?!
by tedrek (Pilgrim) on Aug 08, 2003 at 18:01 UTC

    I just wanted to say THANK YOU, I was just bitten by this, and would never have figured it out if it wasn't for this thread. In my case the while appears to have been in a module I was using.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://281699]
Approved by phydeauxarff
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others pondering the Monastery: (20)
As of 2014-04-16 17:35 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (433 votes), past polls