perl.gg / secret-operators

<!-- category: secret-operators -->

Two-Dot vs Three-Dot Flip-Flop (.. vs ...)

2026-03-13

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:
while (<DATA>) { print if m~START~ .. m~END~; } __DATA__ noise START grab this and this END more noise
Output:
START grab this and this END
The .. 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:

.. Can flip ON and OFF on the same line ... Guarantees at least two evaluations (the trigger line plus the next)
This only matters when both conditions can match the same line. Let's build that scenario.

Part 3: THE SAME-LINE PROBLEM

Consider a file where a line contains both your start and end patterns:
while (<DATA>) { print "TWO: $_" if m~BEGIN~ .. m~END~; } __DATA__ line one BEGIN something END line two BEGIN only middle END only line three
Output:
TWO: BEGIN something END TWO: BEGIN only TWO: middle TWO: END only
Now watch what happened with that first match. "BEGIN something END" matched both conditions on the same line. With .., 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

while (<DATA>) { print "THREE: $_" if m~BEGIN~ ... m~END~; } __DATA__ line one BEGIN something END line two BEGIN only middle END only line three
Output:
THREE: BEGIN something END THREE: line two THREE: BEGIN only THREE: middle THREE: END only
See the difference? With ..., 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

# Looking for <item>...</item> blocks while (<$xml>) { print if m~<item>~ .. m~</item>~; }
If your XML has <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

# Extract log entries from 10:00 to 11:00 while (<$log>) { print if m~\b10:00\b~ .. m~\b11:00\b~; }
What if a single log line says "Transition from 10:00 to 11:00 shift"? With .., 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

while (<DATA>) { print if m~MARK~ .. m~MARK~; } __DATA__ one MARK two MARK three
Same pattern on both sides. With .., 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.

.. output: MARK, MARK ... output: MARK, two, MARK
That's a huge behavioral difference from a single extra dot.

Part 6: A SIDE-BY-SIDE LABORATORY

Let's be systematic. Here's a test script that runs both versions on the same data:
#!/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~; }
Output:
=== Two-dot (..) === A and B === Three-dot (...) === A and B middle just B
With .., "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 $.):
while (<DATA>) { print if 3 .. 3; # Only line 3 }
With two dots, 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.
while (<DATA>) { print if 3 ... 3; # Line 3 and beyond?! }
With three dots, 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.
while (<DATA>) { my $val = (m~START~ .. m~END~); print "$val: $_" if $val; } __DATA__ noise START line A line B END noise
Output:
1: START 2: line A 3: line B 4E0: END
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.

This means you can detect the first and last lines of a range:

while (<DATA>) { if (my $n = (m~START~ .. m~END~)) { if ($n == 1) { say ">>> Range begins"; } if ($n =~ m~E0$~) { say "<<< Range ends"; } say $_; } }
The 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:

Use ... (three dots) when:

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 from sed and awk. The two-dot version mimics sed's range addressing:
sed -n '/START/,/END/p' file.txt
The three-dot version is Perl's own invention, added because Larry Wall realized the 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.

.. ... / \ / \ ON OFF ON (wait) \ / \ \ \/ \ OFF (same (next line) line) Two dots: Three dots: impatient patient
perl.gg