<!-- category: hidden-gems -->
The continue Block
Perl has afinally for loops. It runs after every iteration, even when you next. Almost nobody knows it exists.
for my $i (1 .. 5) { next if $i == 3; say "Processing $i"; } continue { say " (iteration $i done)"; }
See iteration 3? The main body was skipped byProcessing 1 (iteration 1 done) Processing 2 (iteration 2 done) (iteration 3 done) Processing 4 (iteration 4 done) Processing 5 (iteration 5 done)
next, but the continue block still ran. That's the whole point. It's code that executes after every iteration, no matter how the iteration ended.
Unless you use last or redo. Those skip it. But next? The continue block catches it. Every time.
Part 1: THE BASIC SYNTAX
Acontinue block attaches to any loop. It goes right after the closing brace of the loop body:
while ($condition) { # loop body } continue { # runs after every iteration }
for my $item (@list) { # loop body } continue { # runs after every iteration }
It works withforeach (@data) { # loop body } continue { # runs after every iteration }
while, for, foreach, and until. The syntax is always the same: the continue keyword and a block, immediately after the loop's closing brace.
Part 2: WHEN IT RUNS AND WHEN IT DOESN'T
Three loop control keywords. Three behaviors:Let's prove it:KEYWORD CONTINUE BLOCK RUNS? WHAT HAPPENS ------- --------------------- --------------------------- next YES Skip rest of body, run continue, next iteration last NO Exit loop entirely, skip continue redo NO Restart iteration from top, skip continue
for my $n (1 .. 5) { if ($n == 2) { say " next on $n"; next; } if ($n == 4) { say " last on $n"; last; } say " body: $n"; } continue { say " continue: $n"; } say "loop done";
Iteration 1 and 3: normal flow, continue runs. Iteration 2:body: 1 continue: 1 next on 2 continue: 2 body: 3 continue: 3 last on 4 loop done
next skipped the body, but continue still ran. Iteration 4: last exited the loop, continue did not run. Iteration 5: never reached because last already exited.
Part 3: THE C FOR-LOOP CONNECTION
If you know C, this should feel familiar. The Cfor loop has three parts:
The third part (increment) runs after every iteration, even when you usefor (init; test; increment) { body; }
continue (C's equivalent of Perl's next). Perl's continue block is exactly that third expression, but for any loop, not just for.
A C-style counted loop in Perl:
Without the continue block,my $i = 0; while ($i < 10) { next if $i == 5; # skip 5 say $i; } continue { $i++; # always increments, even on next }
next would skip $i++, and you'd get an infinite loop when $i hits 5. The continue block guarantees the counter always advances. This is the original use case.
Part 4: LOOP COUNTERS
The most practical use. You want a counter that tracks how many iterations have happened, regardless ofnext:
The progress report runs every 100 iterations, whether the file was processed or skipped. If you put the progress check inside the loop body,my $processed = 0; my $skipped = 0; for my $file (@files) { unless (-f $file) { $skipped++; next; } process_file($file); $processed++; } continue { if (($processed + $skipped) % 100 == 0) { say "Progress: $processed processed, $skipped skipped"; } } say "Final: $processed processed, $skipped skipped";
next would bypass it.
Part 5: PROGRESS REPORTING
For long-running loops, you want periodic status updates that never get skipped:Threemy $start = time(); my $count = 0; while (my $line = <$fh>) { chomp $line; # various reasons to skip next if $line =~ m~^#~; # comments next if $line =~ m~^\s*$~; # blank lines next unless $line =~ m~\t~; # needs a tab process($line); } continue { $count++; if ($count % 10_000 == 0) { my $elapsed = time() - $start; printf STDERR "\r%d lines in %ds (%.0f lines/sec)", $count, $elapsed, $count / ($elapsed || 1); } } say STDERR "\nDone. $count total lines.";
next statements in the body. The progress counter in continue sees every line, skipped or not. Without continue, you'd need to increment the counter before every next call, which means three copies of the same code.
Part 6: RESOURCE CLEANUP
Clean up per-iteration resources even when the body bails early:Twofor my $url (@urls) { my $tmpfile = "/tmp/download_$$.tmp"; open my $fh, '>', $tmpfile or do { warn "Cannot write $tmpfile: $!\n"; next }; my $response = fetch($url); unless ($response->{success}) { warn "Failed: $url\n"; next; } print $fh $response->{body}; close $fh; process($tmpfile); } continue { # always clean up the temp file, even on next my $tmpfile = "/tmp/download_$$.tmp"; unlink $tmpfile if -e $tmpfile; }
next calls in the body. Both skip the cleanup at the end. But the continue block catches them. The temp file always gets deleted.
Is this better than a separate cleanup function? Maybe not. But it keeps the cleanup visually attached to the loop it belongs to. No hunting for a subroutine definition. The intent is obvious: "after every iteration, no matter what, do this."
Part 7: THE WHILE-READLINE PATTERN
There's a famous Perl idiom usingcontinue with while (<>):
Thewhile (<>) { # process lines from files in @ARGV chomp; say "Line $.: $_" if m~ERROR~; } continue { close ARGV if eof; # reset $. between files }
close ARGV if eof trick resets the line counter $. when Perl finishes one file and moves to the next. Without it, $. just keeps incrementing across all files. With it, $. restarts at 1 for each file.
This is documented in perldoc -f eof and it specifically recommends the continue block for it. It's one of the rare places where the official Perl documentation says "use continue."
Part 8: COMBINING WITH LOOP LABELS
continue works with labeled loops:
Each loop can have its ownOUTER: for my $dir (@dirs) { opendir my $dh, $dir or next; INNER: while (my $file = readdir $dh) { next INNER if $file =~ m~^\.~; next OUTER unless -f "$dir/$file"; process("$dir/$file"); } continue { # this continue belongs to INNER } closedir $dh; } continue { # this continue belongs to OUTER say "Finished directory: $dir"; }
continue block. The next INNER triggers the inner continue. The next OUTER skips the inner continue and triggers the outer one.
Part 9: WHY ALMOST NOBODY USES IT
Thecontinue block is documented. It's been in Perl since version 5. It works exactly as advertised. And almost nobody uses it.
Why?
Most people don't know it exists. It's buried in perlsyn between loop modifiers and basic loops. No tutorial teaches it. No beginner book highlights it. It's there, but invisible.
The workaround is easy. Instead of continue, most people just put the code before every next:
This works until you forget onefor my $item (@list) { $count++; # before every next next unless valid($item); $count_again++; # whoops, wrong count process($item); }
next path. Then your counter is wrong and you spend thirty minutes debugging.
Perl culture moved on. Modern Perl emphasizes subroutines, closures, and higher-order functions over loop mechanics. The continue block is a procedural-era construct. It's not wrong, it's just old-school.
But for its intended use case, loop-invariant post-iteration code, nothing else is as clean. Not a separate function call. Not duplicated code before every next. The continue block is the right tool.
Part 10: THE LOOP LIFECYCLE
TheSTART | v +---------+ | test |---false---> EXIT LOOP | cond | +---------+ | true | v +---------+ | BODY |---last---> EXIT LOOP (skip continue) | |---redo---> back to BODY (skip continue) | |---next--+ +---------+ | | | v v +-----------+ | CONTINUE | <-- runs on normal end AND on next +-----------+ | v (back to test) .--. |o_o | "next skips the body. |:_/ | It does not skip me." // \ \ -- continue block (| | ) /'\_ _/`\ \___)=(___/
continue block is Perl's per-iteration cleanup mechanism. It runs after every iteration that doesn't exit the loop entirely. next triggers it. Normal flow triggers it. Only last and redo bypass it.
It solves a real problem: code that must run after every iteration, regardless of how the iteration ended. Counters, progress bars, cleanup, resource release, line-number resets. All of these belong in continue.
Most Perl programmers will go their entire career without writing one. But knowing it exists means you have a clean answer for the next time you find yourself copy-pasting a counter increment before every next statement in a loop.
perl.gg