Skip to content
VLSI Mentor

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

Azvya Education Pvt. Ltd.VLSI Mentor
whosets.pl
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";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
for   : a b
grep  : ax ay
map   : A B
while : [one] [two]
ConstructSets $_ to
for (LIST) / foreach (LIST)each item in turn
grep { } LISTeach item, while testing
map { } LISTeach 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.

Azvya Education Pvt. Ltd.VLSI Mentor
localise.pl
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";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
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:

Azvya Education Pvt. Ltd.VLSI Mentor
nested-ok.pl
use strict;
use warnings;

for ("outer1", "outer2") {
    my $outer = $_;
    for ("x", "y") { }
    print "outer item was [$outer], \$_ is still [$_]\n";
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
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.

Azvya Education Pvt. Ltd.VLSI Mentor
no-localise.pl
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");
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
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 $_ reads undef. With use warnings that produces Use 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 a for (@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 not for inside for that bites, it is a file-reading while inside anything.
Azvya Education Pvt. Ltd.VLSI Mentor
The shape to avoid
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:

Azvya Education Pvt. Ltd.VLSI Mentor
The fix, and it is the general rule
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 $_

Azvya Education Pvt. Ltd.VLSI Mentor
sub-clobber.pl
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);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
the array is now [CLOBBERED|CLOBBERED]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet

The array was destroyed, and nothing warned.

Two facts combine to produce that, and neither is a bug on its own:

  1. $_ is a global, so clobber's assignment reaches the same variable the loop is using. Unlike §2's constructs, a bare assignment does not localise anything.
  2. 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Good uses — the value is obvious and short-lived
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;
Azvya Education Pvt. Ltd.VLSI Mentor
Name it — the value has to survive something
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 body

The 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, map and while (<$fh>) all set $_ when you name no variable, and many builtins default to it.
  • for, grep and map localise it — measured, all three restored a prior BEFORE value. 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 is while (defined($_ = <$fh>)), so after the loop $_ holds the undef that ended it — not the last line.
  • So the real nesting hazard is a file-reading while inside an unnamed outer loop. Name the outer variable.
  • A subroutine that assigns to $_ without local rewrites 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.