<!-- category: secret-operators -->
Two-Dot vs Three-Dot Flip-Flop (.. vs ...)
You already know the flip-flop operator. Two dots in scalar context. It turns on when the left condition matches, turns off when the right condition matches, and everything in between gets captured.But did you know there are actually two flip-flop operators? The two-dot .. and the three-dot .... They behave differently in one critical edge case, and almost nobody knows about the three-dot version.
This is the kind of distinction that separates Perl hackers from Perl wizards.
Part 1: QUICK RECAP
The flip-flop in its natural habitat:Output:while (<DATA>) { print if m~START~ .. m~END~; } __DATA__ noise START grab this and this END more noise
TheSTART grab this and this END
.. flips ON when it sees START. It stays on, returning true for every line. When it sees END, it returns true one last time and flips OFF. Simple.
For 95% of use cases, that's all you need to know. But the remaining 5% is where things get interesting.
Part 2: THE CRITICAL DIFFERENCE
Here's the one-sentence summary:.. checks the right-hand condition on the same line the left-hand condition triggers. ... waits until the next line to start checking the right-hand condition.
In other words:
This only matters when both conditions can match the same line. Let's build that scenario... Can flip ON and OFF on the same line ... Guarantees at least two evaluations (the trigger line plus the next)
Part 3: THE SAME-LINE PROBLEM
Consider a file where a line contains both your start and end patterns:Output:while (<DATA>) { print "TWO: $_" if m~BEGIN~ .. m~END~; } __DATA__ line one BEGIN something END line two BEGIN only middle END only line three
Now watch what happened with that first match. "BEGIN something END" matched both conditions on the same line. WithTWO: BEGIN something END TWO: BEGIN only TWO: middle TWO: END only
.., Perl checks the left side, sees it's true, flips ON, then immediately checks the right side on that same line, sees it's also true, and flips OFF.
The flip-flop turned on and off in a single line. It returned true for that line (so it printed), but it was already off again by the next line.
This is correct behavior. But sometimes it's not what you want.
Part 4: THREE DOTS TO THE RESCUE
Output:while (<DATA>) { print "THREE: $_" if m~BEGIN~ ... m~END~; } __DATA__ line one BEGIN something END line two BEGIN only middle END only line three
See the difference? WithTHREE: BEGIN something END THREE: line two THREE: BEGIN only THREE: middle THREE: END only
..., when "BEGIN something END" triggers the left condition, Perl does NOT check the right condition on that same line. It waits. So the flip-flop stays ON.
"line two" gets printed because the flip-flop is still on. Then "BEGIN only" triggers the left condition again (which is a no-op since we're already on). "middle" passes through. "END only" finally triggers the right condition and flips it OFF.
LINE .. (two-dot) ... (three-dot) ----------------------------------------------------------------- line one OFF OFF BEGIN something END ON -> OFF (same!) ON (stays on!) line two OFF ON (still on) BEGIN only ON ON middle ON ON END only ON -> OFF ON -> OFF line three OFF OFF
Part 5: WHEN DOES THIS ACTUALLY MATTER?
Honestly? Rarely. Most of the time your start and end patterns are on different lines. Config file sections, log blocks, function definitions. The markers are separate.But there are real cases where it bites:
Case 1: Single-line XML elements
If your XML has# Looking for <item>...</item> blocks while (<$xml>) { print if m~<item>~ .. m~</item>~; }
<item>short value</item> all on one line, the two-dot version handles it fine (prints the line, moves on). But if you expected multi-line items and a single-line one slips through, the two-dot version flips on and off silently while three dots would keep going until it finds a </item> on a future line.
Which one is "correct" depends entirely on your data and your expectations.
Case 2: Log entries with timestamps
What if a single log line says "Transition from 10:00 to 11:00 shift"? With# Extract log entries from 10:00 to 11:00 while (<$log>) { print if m~\b10:00\b~ .. m~\b11:00\b~; }
.., that one line prints and the flip-flop resets. With ..., everything from that line until the next line containing "11:00" gets printed.
Case 3: The degenerate case
Same pattern on both sides. Withwhile (<DATA>) { print if m~MARK~ .. m~MARK~; } __DATA__ one MARK two MARK three
.., every MARK line flips on and immediately off (same condition, same line). You only get the MARK lines themselves.
With ..., the first MARK flips on. The next line starts checking for MARK. "two" doesn't match, stays on. The second MARK matches, flips off.
That's a huge behavioral difference from a single extra dot... output: MARK, MARK ... output: MARK, two, MARK
Part 6: A SIDE-BY-SIDE LABORATORY
Let's be systematic. Here's a test script that runs both versions on the same data:Output:#!/usr/bin/env perl use strict; use warnings; use feature 'say'; my @lines = ( "before", "A and B", "middle", "just B", "after", ); say "=== Two-dot (..) ==="; for (@lines) { say " $_" if m~A~ .. m~B~; } say "\n=== Three-dot (...) ==="; for (@lines) { say " $_" if m~A~ ... m~B~; }
With=== Two-dot (..) === A and B === Three-dot (...) === A and B middle just B
.., "A and B" matches both sides on the same line. On, then off. Done.
With ..., "A and B" matches the left side, flips on. Right side is NOT checked until the next iteration. "middle" doesn't match B, stays on. "just B" matches B, prints, flips off.
One dot makes all the difference.
Part 7: LINE NUMBERS AND FLIP-FLOPS
Both.. and ... work with line numbers (via $.):
With two dots,while (<DATA>) { print if 3 .. 3; # Only line 3 }
3 .. 3 turns on at line 3, checks the right side on the same line, sees it's also true, turns off. You get exactly line 3.
With three dots,while (<DATA>) { print if 3 ... 3; # Line 3 and beyond?! }
3 ... 3 turns on at line 3, does NOT check the right side until line 4. At line 4, $. == 3 is false. At line 5, false. It never turns off because $. will never be 3 again.
Warning: 3 ... 3 with three dots will print from line 3 to the end of the file. The right condition can never trigger because by the time it's checked, $. has moved past 3. This is almost certainly a bug if you write it, but it's a great illustration of the behavioral difference.
.. ... ~~ ~~~ Two-dot: Three-dot: "Am I done?" "Let me sleep (checks NOW) on it first" (checks NEXT)
Part 8: THE RETURN VALUE
Here's a subtlety most people miss. The flip-flop doesn't just return true or false. It returns a sequence number.Output:while (<DATA>) { my $val = (m~START~ .. m~END~); print "$val: $_" if $val; } __DATA__ noise START line A line B END noise
The return value is a counter. It starts at 1 when the flip-flop turns on, increments for each true evaluation, and on the final line (when it turns off), it appends "E0" to the value.1: START 2: line A 3: line B 4E0: END
This means you can detect the first and last lines of a range:
Thewhile (<DATA>) { if (my $n = (m~START~ .. m~END~)) { if ($n == 1) { say ">>> Range begins"; } if ($n =~ m~E0$~) { say "<<< Range ends"; } say $_; } }
E0 suffix is not a number, but it's true in boolean context and can be pattern-matched. Perl being Perl.
This behavior is identical for .. and .... The difference is only about when the right side gets checked.
Part 9: PRACTICAL ADVICE
Here's when to use which:Use .. (two dots) when:
- Your start and end markers are always on separate lines
- You want single-line matches to flip on and off cleanly
- You're doing line-number ranges like
5 .. 10 - You're not sure (it's the safer default for most cases)
Use ... (three dots) when:
- A line might match both start and end patterns
- You want the flip-flop to stay on for at least one line after triggering
- You're working with messy data where markers aren't clean
- You explicitly want "start on this line, don't check for end until the next one"
For most sysadmin work (log parsing, config extraction, text processing), two dots is fine. The three-dot version is the specialist tool you reach for when two dots gives you surprising results.
Part 10: ORIGINS AND HISTORY
Both operators come fromsed and awk. The two-dot version mimics sed's range addressing:
The three-dot version is Perl's own invention, added because Larry Wall realized thesed -n '/START/,/END/p' file.txt
sed behavior had an edge case that could surprise people. The three-dot version is the "kinder, gentler" flip-flop.
In awk, the range pattern /START/,/END/ always behaves like Perl's ... (three dots). The start and end conditions are never checked on the same record. So if you're coming from awk, the three-dot version matches your intuition.
If you're coming from sed, the two-dot version matches yours.
Larry gave you both and let you choose. That's the Perl way.
perl.gg.. ... / \ / \ ON OFF ON (wait) \ / \ \ \/ \ OFF (same (next line) line) Two dots: Three dots: impatient patient