<!-- category: secret-operators -->
Bare Number Flip-Flop
Print lines 5 through 10 of a file:No regex. No comparison operator. Just two bare numbers and aperl -ne 'print if 5 .. 10'
.. between them. And somehow Perl knows you mean line numbers.
This is one of Perl's quietest tricks. When the flip-flop operator .. sees a bare integer on either side, it silently compares that integer against $., the current input line number. The expression 5 .. 10 is really $. == 5 .. $. == 10.
You never asked for it. You never wrote it. Perl just does it.
Part 1: THE FLIP-FLOP BASICS
In list context,.. is the range operator:
In scalar context (like inside anmy @nums = (1 .. 5); # (1, 2, 3, 4, 5)
if condition), it becomes the flip-flop operator. It remembers state between calls. It starts in the OFF state. When the left condition becomes true, it flips ON. When the right condition becomes true, it flops back OFF.
That prints everything between START and END, inclusive. The flip-flop turns on at START and off at END.while (<DATA>) { print if m~START~ .. m~END~; }
Part 2: THE BARE NUMBER MAGIC
Now here's the trick. When you put a bare number where the flip-flop expects a condition, Perl doesn't treat it as a boolean. It compares it to$..
Perl reads that as:while (<>) { print if 5 .. 10; }
On line 5,while (<>) { print if $. == 5 .. $. == 10; }
$. == 5 is true, so the flip-flop turns ON. Lines 5, 6, 7, 8, 9 are printed (flip-flop stays ON). On line 10, $. == 10 is true, so the flip-flop turns OFF after printing line 10.
You get lines 5 through 10. Exactly like sed -n '5,10p'.
Part 3: WHY THIS WORKS
The$. variable is the current line number. It increments every time you read from any filehandle with <>.
The flip-flop has a special case baked into the Perl parser. Fromwhile (<>) { print "Line $.: $_"; }
perlop:
> If either operand of scalar .. is a constant expression, that operand is considered true if it is equal (==) to the current input line number (the $. variable).
"Constant expression" means a literal number. Not a variable. Not a calculation. Just a plain integer sitting there. When Perl sees it, the implicit $. == comparison kicks in.
This isn't a feature anyone designed by committee. It's inherited from sed and awk, where line-number ranges were first-class citizens. Larry Wall carried the idea into Perl and made it work through the flip-flop.
Part 4: ONE-LINER RECIPES
Print lines 5 through 10:Print the first 20 lines (likeperl -ne 'print if 5 .. 10' file.txt
head -20):
Skip the first line (header) and print the rest:perl -ne 'print if 1 .. 20' file.txt
Print a single line (line 42):perl -ne 'print if 2 .. eof' file.txt
That last one is silly. You'd normally writeperl -ne 'print if 42 .. 42' file.txt
perl -ne 'print if $. == 42'. But it shows that the flip-flop works with identical endpoints.
Print lines 10 to the end of the file:
Theperl -ne 'print if 10 .. eof' file.txt
eof function returns true when there's no more input. So the flip-flop turns on at line 10 and never turns off.
Part 5: MIXING WITH REGEX
You can mix bare numbers with regex patterns on either side of the flip-flop:# from line 1 until a blank line perl -ne 'print if 1 .. m~^$~' file.txt
# from a pattern to line 50 perl -ne 'print if m~BEGIN~ .. 50' file.txt
The left and right sides of# from line 10 to a pattern perl -ne 'print if 10 .. m~END~' file.txt
.. are independent. One can be a number (implicitly compared to $.), the other can be a regex, a function call, or any boolean expression.
Start printing at line 5, stop when you hit a line that's justwhile (<>) { print if 5 .. m~^---~; }
---. The bare 5 triggers the $. == magic. The regex is evaluated normally.
Part 6: THE THREE-DOT VARIANT
There's also... (three dots). The difference is subtle.
With .. (two dots), if the left side and right side both become true on the same line, the flip-flop turns on AND off in one step. With ... (three dots), when the left side becomes true, the right side isn't checked until the NEXT iteration.
In practice this rarely matters for bare number ranges, because# two dots: if line 5 also matches END, it turns on and off print if 5 .. m~END~; # three dots: if line 5 matches END, it stays on until NEXT END print if 5 ... m~END~;
5 .. 10 can't have both sides true on the same line (a line number can't be 5 and 10 simultaneously). But it matters when you mix a number with a regex:
When in doubt, two dots is the default. Use three dots when you know the off-condition might match the same line as the on-condition and you want to keep going.# file where line 5 happens to say "END" # two dots: prints only line 5 (turns on and off on same line) # three dots: prints from line 5 until the NEXT "END"
Part 7: WHAT COUNTS AS "BARE"
The implicit$. comparison triggers only for constant expressions. These work:
These do NOT trigger the magic:print if 5 .. 10; # bare integers, magic applies print if 1 .. 100; # bare integers, magic applies
The safest rule: if it's a literal integer typed directly into the code, the magic happens. If it's anything else, it doesn't.my $start = 5; print if $start .. 10; # variable, no magic, boolean eval print if (2+3) .. 10; # expression, no magic (actually, this # IS constant-folded, so it depends on # the Perl version)
Part 8: MULTIPLE RANGES
The flip-flop resets after turning off, so you can catch multiple ranges:But wait. Can you have multiple different flip-flop ranges in the same loop? Yes. Eachwhile (<>) { print if 3 .. 5; print "---\n" if $. == 5; }
.. expression has its own independent state:
Lines 1-3 get the HEADER prefix. Lines 10-15 get the DATA prefix. Two independent flip-flops, each tracking its own state.while (<>) { if (1 .. 3) { print "HEADER: $_"; } if (10 .. 15) { print "DATA: $_"; } }
Part 9: PRACTICAL USES
Extract a function from source code by line numbers (you got them fromgrep -n):
Start at line 142 (the bare number magic), stop at the first closing brace at the start of a line. You just extracted the function.grep -n 'sub process_data' code.pl # output: 142:sub process_data perl -ne 'print if 142 .. m~^}~' code.pl
Print the 5 lines around line 200 (context):
Skip a known header in a data file:perl -ne 'print if 195 .. 205' huge.log
Print every other block of 10 lines:# CSV with 3 header lines perl -ne 'print if 4 .. eof' data.csv
OK, that last one doesn't use the flip-flop. But it shows that once you know aboutperl -ne 'print if int(($.-1)/10) % 2 == 0' file.txt
$., a whole world of line-number tricks opens up.
Part 10: THE $. VARIABLE
Since bare numbers in flip-flops compare against$., it's worth knowing $. well.
That last point is important. If you process multiple files with$. Current line number of last filehandle read Increments with each <> read Resets when you explicitly close a filehandle Does NOT auto-reset between files in @ARGV
<>, $. keeps counting across files unless you close ARGV:
To reset per file:# file1.txt has 10 lines, file2.txt has 10 lines perl -ne 'print "$.: $_"' file1.txt file2.txt # lines 1-10 from file1, lines 11-20 from file2
This affects bare-number flip-flops too.perl -ne 'print "$.: $_"; close ARGV if eof' file1.txt file2.txt # lines 1-10 from file1, lines 1-10 from file2
print if 1 .. 5 will only trigger once across all files unless you reset $..
The bare number flip-flop is a relic of Perl's sed/awk heritage. It saves a few keystrokes and looks like magic to anyone who hasn't read.----. | $. | '------' || [1][2][3]...[N] || 5 .. 10 means $. == 5 .. $. == 10 Invisible magic. Two numbers. One variable. Perl reads your mind. .--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/
perlop carefully. But once you know the rule (bare integer + .. = implicit $. comparison), it stops being magic and starts being a tool.
A really convenient tool.
perl.gg