Perl Part I · Module 9 · Loops
$_ : The Variable Perl Uses When You Name None
Perl lets you leave the loop variable out, and several constructs then put the current value into a variable called $_. That is what makes a one-line transform possible at all, and it is why so much Perl reads as though values are flowing through the code without being named. The reputation $_ has is that it is dangerous when nested, and that reputation is largely wrong — the constructs that set it almost all put it back afterwards, which this chapter measures rather than assumes. There is a real hazard, it is narrower than the folklore, and it is worse: one specific thing a subroutine can do turns a loop that was reading an array into a loop that overwrites every element of it.
Foundation16 min readPerlPart IControl FlowLoops
Part I · Module 9 · Chapter 9.7 · $_ : The Variable Perl Uses When You Name None
1. Four Constructs Set It
use strict;
use warnings;
print "for :";
print " $_" for ("a", "b");
print "\n";
print "grep :";
print " $_" for grep { /a/ } ("ax", "b", "ay");
print "\n";
print "map :";
print " $_" for map { uc } ("a", "b");
print "\n";
open my $fh, "<", "two.txt" or die "cannot open: $!";
print "while :";
while (<$fh>) { chomp; print " [$_]" }
close $fh;
print "\n";for : a b
grep : ax ay
map : A B
while : [one] [two]| Construct | Sets $_ to |
|---|---|
for (LIST) / foreach (LIST) | each item in turn |
grep { } LIST | each item, while testing |
map { } LIST | each item, while transforming |
while (<$fh>) | each line read |
Note the map block is just uc with no argument. Many builtins default to $_ when given none — uc, lc, chomp, length, print, split on its third argument — which is the other half of why $_ makes code short. print " $_" for ... could be written print " $_" foreach ... identically.
while (<$fh>) with no assignment is shorthand for while (defined($_ = <$fh>)), which Chapter 9.3 §3 proved with -MO=Deparse. That is why a line of 0 still reads correctly here.
2. Three of Them Put It Back
This is where the folklore and the measurement part company.
use strict;
use warnings;
$_ = "BEFORE";
for ("a", "b") { }
print "after for : [$_]\n";
$_ = "BEFORE";
my @g = grep { /a/ } ("ax", "b");
print "after grep : [$_]\n";
$_ = "BEFORE";
my @m = map { uc } ("c");
print "after map : [$_]\n";after for : [BEFORE]
after grep : [BEFORE]
after map : [BEFORE]All three restored it. for, grep and map localise $_ — they save the old value, use their own, and put the original back when they finish. That is the same implicit localisation Chapter 9.1 §4 measured for a named loop variable.
The direct consequence is that nesting them is safe:
use strict;
use warnings;
for ("outer1", "outer2") {
my $outer = $_;
for ("x", "y") { }
print "outer item was [$outer], \$_ is still [$_]\n";
}outer item was [outer1], $_ is still [outer1]
outer item was [outer2], $_ is still [outer2]The inner loop ran to completion between the two reads of $_, and the outer $_ was intact afterwards. A grep or map in that position behaves the same way.
3. while (<$fh>) Does Not Put It Back
One of the four is different.
use strict;
use warnings;
$_ = "BEFORE";
open my $fh, "<", "two.txt" or die "cannot open: $!";
while (<$fh>) { }
close $fh;
printf "after while(<>) : %s\n", (defined $_ ? "[$_]" : "UNDEF");after while(<>) : UNDEF$_ is not BEFORE, and it is not the last line either — it is undef.
The reason follows directly from §1's expansion. while (<$fh>) means while (defined($_ = <$fh>)), which is a plain assignment to the global $_ on every pass — including the final pass, where readline returns undef, that undef is assigned, and defined then ends the loop. So the loop leaves $_ holding the value that stopped it.
Two practical consequences:
- Anything after a
while (<$fh>)loop that reads$_readsundef. Withuse warningsthat producesUse of uninitialized value $_at the point of use, which is a clear enough signal — but only if you are looking at STDERR. - A
while (<$fh>)loop nested inside afor (@items)loop destroys the outer item. This is the one genuine nesting hazard in the chapter, and it is the reverse of the folklore: it is notforinsideforthat bites, it is a file-readingwhileinside anything.
for (@files) {
open my $fh, "<", $_ or next;
while (<$fh>) { ... } # clobbers the outer $_
close $fh;
print "finished $_\n"; # $_ is now undef, not the filename
}Name the variable in the outer loop and the problem disappears:
for my $file (@files) {
open my $fh, "<", $file or next;
while (<$fh>) { ... }
close $fh;
print "finished $file\n";
}That is the rule worth adopting generally: use $_ for the innermost, shortest thing, and name anything that has to survive across a call or another loop.
4. The Real Hazard: a Subroutine That Assigns to $_
use strict;
use warnings;
sub clobber { $_ = "CLOBBERED"; return 1 }
my @blocks = ("alu", "fifo");
for (@blocks) {
clobber();
}
printf "the array is now [%s]\n", join("|", @blocks);the array is now [CLOBBERED|CLOBBERED]The array was destroyed, and nothing warned.
Two facts combine to produce that, and neither is a bug on its own:
$_is a global, soclobber's assignment reaches the same variable the loop is using. Unlike §2's constructs, a bare assignment does not localise anything.- The loop's
$_is an alias to the array element — Chapter 9.2's finding. So assigning to$_assigns to$blocks[0], then to$blocks[1].
Either one alone is survivable. A global assignment with no aliasing would leave a stray value in $_; aliasing without a global would be confined to the loop body you can see. Together, a subroutine three files away rewrote an array it was never passed.
5. When to Use It and When to Name It
$_ earns its place when the thing it names is obvious from one line of context.
print "$_\n" for sort keys %report;
my @failing = grep { $_->{slack} < 0 } @rows;
my @names = map { $_->{block} } @rows;
chomp for @lines;
s/\s+$// for @lines;
my $total = 0;
$total += $_ for @paths;for my $file (@files) { ... open ... while (<$fh>) ... } # another loop
for my $blk (@blocks) { ... check($blk) ... report($blk) } # across calls
for my $row (@rows) { ... many lines of body ... } # long bodyThe test is three questions:
- Does anything between the uses of the value call a subroutine or open another loop? Name it.
- Is the body more than about three lines? Name it. A reader scrolling past the loop header has to remember what
$_is. - Is there a second nested construct? Name at least the outer one.
Note that grep and map blocks are exempt from the length rule in practice, because they are expressions and almost always one line. A map block long enough to need a named variable is usually a for loop written as a map.
6. Why This Matters in Engineering Work
It is most of why Perl is short. chomp for @lines; and grep { $_->{slack} < 0 } @rows are one line each because $_ exists. The alternative is a named variable in both the header and the body, which on a one-line transform is more punctuation than content.
The nesting fear is misplaced, and that is useful to know. for, grep, map and a subroutine that merely uses $_ are all safe — measured. Avoiding $_ entirely on nesting grounds costs you concision for no benefit.
The two cases that do bite are specific. A file-reading while inside an unnamed outer loop leaves the outer value undef; a subroutine that assigns to $_ without local rewrites the caller's array through the alias. Both are avoided by one habit each — name the outer variable, and never assign to $_ in a subroutine.
And the array-rewrite case is the worst failure shape in this module. No warning, no error, exit 0, and the data corrupted by a function that was not passed it.
7. Common Mistakes
Believing nested for loops clobber $_. They do not — all three of for, grep and map localise it.
Leaving the outer loop variable unnamed around a while (<$fh>). That loop does not localise, and leaves $_ as undef.
Expecting $_ after a file loop to hold the last line. It holds the undef that ended the loop.
Assigning to $_ in a subroutine. It reaches the caller's $_, and through the alias, the caller's array.
Expecting the caller to defend against that. It cannot. Use local $_ in the subroutine, or a named variable.
Reading $_ in a subroutine instead of taking an argument. It makes the function depend on invisible caller state.
Using $_ in a long loop body. Name it once the reader has to scroll.
Setting $/ or $, in a subroutine without local. Same class, wider effect.
8. Exercise
Exercise 1 — who sets it. Write one example each of for, grep, map and while (<$fh>) using $_ with no named variable. Confirm all four print what you expect.
Exercise 2 — who restores it. Set $_ = "BEFORE", run each of for, grep and map, and print $_ after each. Record all three results.
Exercise 3 — the one that does not. Set $_ = "BEFORE", run a while (<$fh>) loop over a two-line file, and print $_ afterwards with a defined test. Record what you get and explain it using the expansion from §1.
Exercise 4 — nesting. Reproduce §2's nested loop. Confirm the outer $_ survives. Then replace the inner for with a while (<$fh>) over a file and record what the outer $_ becomes.
Exercise 5 — across a call. Write a subroutine whose body is for (1 .. 2) { } and call it from inside a for (@items) loop, printing $_ after each call. Confirm the caller's value survives.
Exercise 6 — the clobber. Write a subroutine whose body is $_ = "CLOBBERED" and call it from for (@blocks). Print the array afterwards. Confirm STDERR is empty and the exit status is 0.
Exercise 7 — the fix. Add local to that subroutine's assignment and run it again. Confirm the array is intact. Then try defending from the caller side instead and say why that is impractical.
Exercise 8 — name or not. For each of these, decide whether to use $_ or a named variable and give one reason: chomping an array; printing sorted hash keys; looping over files and reading each one; a twelve-line loop body; a grep filtering records by a field.
9. Summary
for,grep,mapandwhile (<$fh>)all set$_when you name no variable, and many builtins default to it.for,grepandmaplocalise it — measured, all three restored a priorBEFOREvalue. Nesting them is safe.- A subroutine that merely uses
$_in its own loop is safe too — the caller's value survived the call. while (<$fh>)does not localise. It iswhile (defined($_ = <$fh>)), so after the loop$_holds theundefthat ended it — not the last line.- So the real nesting hazard is a file-reading
whileinside an unnamed outer loop. Name the outer variable. - A subroutine that assigns to
$_withoutlocalrewrites the caller's array — measured[CLOBBERED|CLOBBERED], empty STDERR, exit 0.$_is global and the loop's$_aliases the element. local $_in the subroutine is the fix. The caller cannot defend against it.- The same applies to
$/,$,,$\and$"— localise any global a subroutine sets. - Use
$_for short, innermost things; name anything that must survive a call, another loop, or more than about three lines.
Chapter 9.8 turns to the loops that do not finish: giving every wait a maximum number of attempts, so a script fails with a clear message instead of hanging.
Where this fits
Part of the Perl curriculum.
