Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Variable being clobbered by a seemingly unrelated while loop

by texh (Initiate)
on Oct 08, 2014 at 05:50 UTC ( [id://1103133]=perlquestion: print w/replies, xml ) Need Help??

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

Hello Monks! Spent a few hours at work on several occasions debugging some scripts that are almost as old as I am, and have found a quirk that I don't really understand. Essentially, we pull a "string of numbers" from an argument given at the command line, and then do various things based on what's contained in that string. An example of the borked code is as follows:
#!/usr/bin/perl # I know there should be # use strict; # and # use warnings; sub process { my ($fileName) = @_; open FILE, $fileName or die $!; while (<FILE>) { chomp $_; my @parts = split(',', $_); for (@parts) { # Do some stuff } } } sub finalize { print "Finished\n"; } my $runlvl = $ARGV[0]; if ($runlvl == 1) { # Do everything $runlvl = "23"; } die "Invalid arguments" unless ($runlvl =~ /^\d+$/); # For conversations sake, lets just read this script my $filename = $0; print "Run level: $runlvl\n"; # Do some stuff based on the runlevel for ($runlvl) { /2/ and process($filename); /3/ and finalize(); } print "Run level: $runlvl\n";
When I run this with a single command line parameter of "1", I'd expect it to get to that poor-man's switch at the bottom and do a process() and finalize(), however only the "2" matches. By the time it gets to the "3", $runlvl is empty. The output of the above is:

$ perl broken.pl 1
Run level: 23
Run level:

Though it took a while to figure out where things were going pear-shaped, the below seems to work as expected. Note the difference in the while() loop in that first subroutine
#!/usr/bin/perl sub process { my ($fileName) = @_; open FILE, $fileName or die $!; while (my $line = <FILE>) { chomp $line; my @parts = split(',', $line); for (@parts) { # Do some stuff } } } sub finalize { print "Finished\n"; } my $runlvl = $ARGV[0]; if ($runlvl == 1) { # Do everything $runlvl = "23"; } die "Invalid arguments" unless ($runlvl =~ /^\d+$/); # For conversations sake, lets just read this script my $filename = $0; print "Run level: $runlvl\n"; # Do some stuff based on the runlevel for ($runlvl) { /2/ and process($filename); /3/ and finalize(); } print "Run level: $runlvl\n";
Wahoo! This time it outputs:

$ perl not_broken.pl 1
Run level: 23
Finished
Run level: 23

So I guess the actual question I want to ask is why is that first while loop that uses the somewhat magick $_ rather than it's own local variable clobbering the value of $runlvl? Or is it clobbering @ARGV? Or.. Is that weird switch thing trying to match on $_ rather than "whatever is in runlevel"? This ones got me stumped - any wisdom would be greatly appreciated.

Replies are listed 'Best First'.
Re: Variable being clobbered by a seemingly unrelated while loop
by blindluke (Hermit) on Oct 08, 2014 at 06:35 UTC

    First of all, your code runs with strict and warnings enabled, no need to comment them out.

    The magic $_ variable is an alias, not a copy. This can be useful when you are iterating over an array (with for (@array)), and modifying $_ - you will see that the array elements will change.

    In your code, the $runlvl variable is clobbered when the while loop starts. You can test this by changing it to a read only value:

    for ('23') { /2/ and process($0); /3/ and finalize(); }

    Now, even without strict and warnings, you will see:

    Modification of a read-only value attempted at ./test.pl line 9.

    ...which corresponds to the beginning of the while loop. The easiest solution would be to change the $_ to a named alias, as in:

    for my $switch ($runlvl) { $switch =~ /2/ and process($0); $switch =~ /3/ and finalize(); }

    There is a very good explanation of the default scalar variable, and the behavior that you are experiencing, in the Modern Perl book (here), which says:

    As English gets confusing when you have too many pronouns and antecedents, so does Perl when you mix explicit and implicit uses of $_. If you use it in multiple places, one piece of code may silently override the value expected by another piece of code. For example, if one function uses $_ and you call it from another function which uses $_, the callee may clobber the caller's value.

    regards,
    Luke Jefferson

Re: Variable being clobbered by a seemingly unrelated while loop
by wrog (Friar) on Oct 08, 2014 at 06:08 UTC

    That last for loop is only executing once and it's assigning $runlvl to $_. The matches within the loop are matching on $_, and apparently the call to process is indeed bashing $_. If you change that last loop to

    for my $xx ($runlvl) { $xx =~ /2/ and process($0); $xx =~ /3/ and finalize(); }

    your problem likewise goes away.

    (which surprises me a bit since I thought $_ was always implicitly localized but apparently not, since if we instead put a local $_; declaration in process() the problem likewise disappears.)

      $_ is localized in for loops, but not while loops. (Indeedy any variable is localized in a for loop. Even lexical variables.)

        Even lexical variables.
        But,
        my $x = 3; local $x'
        leads to
        Can't localize lexical variable $x at 1.pl line 2.
        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
        Right. No I was thinking $_ was implicitly localized in subs, but now I see perlsub explicitly warns against expecting this.
Re: Variable being clobbered by a seemingly unrelated while loop
by Anonymous Monk on Oct 08, 2014 at 06:12 UTC
Re: Variable being clobbered by a seemingly unrelated while loop
by sundialsvc4 (Abbot) on Oct 08, 2014 at 11:59 UTC

    There is, unfortunately, a lot of “old code” just like that.   (Sometimes with commented-out use strict; use warnings; statements, with comments like, “get rid of warnings.”)   All you can really do is to carefully fix the code to use local variables (e.g. foreach (my $foo = ...) and so on), and clean-up the other code that is “too clever.”   It is very messy work because you really can’t be sure that the code in question actually worked before.   Only that it “seemed to.”   It was never rigorously tested.   You can waste a lot of time with this stuff.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others romping around the Monastery: (7)
As of 2024-04-23 14:35 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found