Skip to content
VLSI Mentor

Perl Part I · Module 9 · Loops

while and until

A foreach loop needs to know its list before it starts. A while loop does not — it repeats as long as a condition holds, which is what you need when the amount of work is decided by the data rather than by you: reading a file to its end, draining a queue, retrying until something succeeds. The most common while loop in all of Perl reads a file line by line, and it contains a piece of syntax that is not in the source you wrote. Perl inserts it for you, it is the reason that loop handles a line containing nothing but a zero correctly, and it is inserted in exactly one situation. A loop that looks identical but draws its value from somewhere else does not get it, and stops early on data that is perfectly valid.

Foundation14 min readPerlPart IControl FlowLoops

Part I · Module 9 · Chapter 9.3 · while and until

1. Repeat Until the Condition Fails

Azvya Education Pvt. Ltd.VLSI Mentor
until-loop.pl
use strict;
use warnings;

my $i = 0;
while ($i < 3)  { print "while i=$i "; $i++ }
print "\n";

my $j = 0;
until ($j >= 3) { print "until j=$j "; $j++ }
print "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
while i=0 while i=1 while i=2 
until j=0 until j=1 until j=2

until (COND) is while (! COND), exactly as Chapter 8.2 established for unless and if. The same advice carries over: until reads well for a single positive-sounding condition you are waiting to become true, and badly once the condition contains && or ||, where you have to apply De Morgan's laws to read it.

The condition is tested before each iteration, including the first — so a while whose condition is false at the start runs zero times. Chapter 9.4 covers the one form that does not work that way.

2. The Loop You Will Write Most

Azvya Education Pvt. Ltd.VLSI Mentor
readlines.pl
use strict;
use warnings;

open my $fh, "<", "zero.txt" or die "cannot open: $!";

my $n = 0;
while (my $line = <$fh>) {
    $n++;
    chomp $line;
    print "  line $n: [$line]\n";
}
close $fh;

printf "lines read: %d\n", $n;

The input file is deliberately awkward. Its bytes are:

Azvya Education Pvt. Ltd.VLSI Mentor
zero.txt, byte for byte
0  \n  \n  l  a  s  t  \n

Three lines: the first contains just 0, the second is empty, the third is last.

Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
  line 1: [0]
  line 2: []
  line 3: [last]
lines read: 3

All three lines were read. That should be surprising. Chapter 8.1 §4 established that "0" and "" are both false, and this loop's condition is an assignment whose value is the line — so line 1 should have ended the loop before line 2 ever arrived.

It did not, and the reason is not in the source.

3. Perl Inserts defined() — Proven, Not Asserted

perl -MO=Deparse prints the program as Perl actually compiled it.

Azvya Education Pvt. Ltd.VLSI Mentor
perl -MO=Deparse -e 'open my $fh,
die unless open my $fh, '<', 'x';
while (defined(my $line = readline $fh)) {
    do {
        print $line
    };
}

defined(...) is in the compiled form and not in the source. Perl adds it, because a loop that stopped on a line containing 0 would be wrong essentially every time, and the thing that genuinely signals end-of-file is readline returning undef — not returning something false.

The same insertion happens when the target is $_:

Azvya Education Pvt. Ltd.VLSI Mentor
perl -MO=Deparse -e 'open my $fh,
die unless open my $fh, '<', 'x';
while (defined($_ = readline $fh)) {
    print $_;
}

4. while (1) and an Explicit Exit

Azvya Education Pvt. Ltd.VLSI Mentor
while-one.pl
use strict;
use warnings;

my $tries = 0;
while (1) {
    $tries++;
    last if $tries >= 3;
}
printf "while (1) with last : %d iteration(s)\n", $tries;
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
while (1) with last : 3 iteration(s)

while (1) is a loop with no exit condition of its own, which is appropriate when the real exit depends on something inside the body — a sentinel value, a successful retry, several conditions tested at different points.

Perl optimises while (1) away as a condition, so there is no cost to it, and no warning. That is also the risk: the loop has no bound unless the body provides one. Chapter 9.8 is the chapter on giving every such loop a maximum number of attempts, and it is worth reading before writing one in a script that waits for anything external.

The loop-control keywords themselves — last, next, redo — are Chapter 9.5's subject.

5. Choosing Between while and foreach

Use foreachUse while
the list is known before the loopthe end is decided by the data
iterating an array, a hash's keys, a rangereading a filehandle to EOF
the count is fixeddraining a queue that the body may add to
you want the loop variable scoped and aliasedretrying until success

The practical signal: if you can write the list, use foreach.

One case deserves singling out because the two forms differ in behaviour, not just style:

Azvya Education Pvt. Ltd.VLSI Mentor
A queue the body adds to
my @queue = ("top");

while (@queue) {
    my $item = shift @queue;
    push @queue, children_of($item);     # the queue grows during the loop
}

while (@queue) re-tests the array every pass, so items pushed inside the body are processed. A foreach over @queue would capture the list at the start, and Chapter 9.1 §3's caution applies — the appended items may or may not be visited, which is not a behaviour to rely on. For a worklist that grows, while on the container is the correct shape, and it is how every tree-walk and dependency-closure script is written.

6. Why This Matters in Engineering Work

The file-reading loop is the single most common loop in Perl, and it is correct for free. A tool report line that is just 0, a blank separator line between sections — both read fine, because of the inserted defined.

That correctness does not transfer. A loop that drains a worklist, or calls a subroutine returning records until it returns nothing, looks the same and gets no defined. A legitimate 0 or empty string ends it early, leaving work undone, with nothing on STDERR and exit 0. In a regression runner that means some tests silently never ran, and the summary reports only the ones that did.

And while on the container is what makes a growing worklist work. Dependency closure, hierarchy traversal, and filelist expansion all push onto the queue they are draining, and that is a while (@queue) loop by necessity rather than preference.

7. Common Mistakes

Assuming Perl adds defined to every while (my $x = ...). It adds it only for a readline.

while (my $job = shift @queue). A 0 or "" job ends the loop. Use while (@queue) or an explicit defined.

while (my $r = next_record()). Same problem; a subroutine returning 0 ends it.

Writing a while whose condition the body never changes. It never terminates — Chapter 9.8 covers bounding it.

while (1) with no bound in a script that waits for something external. Give it a maximum attempt count.

until with && or ||. Negation flips the connective; write the while instead.

foreach over a queue the body pushes onto. The list is captured at the start. Use while on the container.

8. Exercise

Exercise 1 — both forms. Write a while and an until that each count 0, 1, 2. Confirm the outputs match and state the relationship between the two conditions.

Exercise 2 — the awkward file. Create a file whose first line is 0, second is empty, third is last. Read it with while (my $line = <$fh>) and print each line bracketed. Confirm all three were read.

Exercise 3 — the proof. Run perl -MO=Deparse on that loop and record the compiled form. Point at the part that is not in your source.

Exercise 4 — the boundary. Run Deparse on while (my $j = shift @w) { } and record the compiled form. State in one sentence what is different and why it matters.

Exercise 5 — the shift trap. Build @work = (3, 0, 5) and drain it two ways: while (my $job = shift @work) and while (@work). Record how many jobs each processed and what was left. Confirm STDERR is empty and the exit status is 0 for both.

Exercise 6 — both fixes. Fix the draining loop twice: once with an explicit defined, once by testing the container. Confirm both process all three jobs.

Exercise 7 — the returning subroutine. Write next_record() that returns 3, then 0, then 5, then undef. Drain it with while (my $r = next_record()) and record how many records you got. Then add defined and record it again.

Exercise 8 — a growing queue. Write a while (@queue) loop that shifts an item and pushes two children for the first item only. Record how many items were processed. Then try the same with foreach over the queue and compare.

9. Summary

  • while repeats while a condition holds; until is while (! COND). The condition is tested before each pass, so a false condition means zero iterations.
  • Perl silently inserts defined() into a readline while — proven with -MO=Deparse: while (my $l = <$fh>) compiles as while (defined(my $l = readline $fh)).
  • That is why a line containing 0 and an empty line are both read correctly. Measured: all 3 lines of a file whose first line is 0.
  • The insertion is for a readline only. while (my $j = shift @queue) gets nothing — Deparse shows the bare assignment.
  • So a 0 in a queue ends the loop: measured 1 job of 3 processed, 2 left, empty STDERR, exit 0.
  • Fix it with an explicit defined, or by testing the container — while (@queue) reads better when the container is to hand.
  • Write defined explicitly even where Perl would insert it, so the code states its own rule.
  • while (1) has no bound unless the body gives it one.
  • Use foreach when you can write the list; use while when the data decides — and while on the container is required for a worklist the body adds to.

Chapter 9.4 covers the one loop form whose condition is tested after the body rather than before it, and why last and next are not allowed inside it.

Where this fits

Part of the Perl curriculum.