╔═╗╦ ╦╔═╗ ╔═╗╦ ╔═╗╔═╗ ╠╣ ║ ║╠═╝ ╠╣ ║ ║ ║╠═╝ ╚ ╩═╝╩╩ ╚ ╩═╝╚═╝╩ ╔═╗╔═╗╔═╗╦═╗╔═╗╔╦╗╔═╗╦═╗ ║ ║╠═╝║╣ ╠╦╝╠═╣ ║ ║ ║╠╦╝ ╚═╝╩ ╚═╝╩╚═╩ ╩ ╩ ╚═╝╩╚═ ============================================================================ You know .. as the range operator: my @nums = (1 .. 5); # (1, 2, 3, 4, 5) But in scalar context, it becomes something completely different: .. The flip-flop operator. It remembers state between calls. Perfect for extracting sections from files. ============================================================================ PART 1: THE TRICK ----------------- while () { print if m~START~ .. m~END~; } __DATA__ ignore this START grab this and this END ignore this too Output: START grab this and this END The flip-flop "turns on" at START and "turns off" at END. Everything between gets printed. ============================================================================ PART 2: HOW IT WORKS -------------------- The .. operator has two states: OFF: Returns false, waiting for left condition to be true ON: Returns true, waiting for right condition to be true When the left side matches, it flips ON. When the right side matches, it flips OFF. It has MEMORY. Each call remembers the previous state. .--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/ ============================================================================ PART 3: THE STATES IN DETAIL ---------------------------- LINE LEFT RIGHT STATE OUTPUT -------------------------------------------------------- ignore false - OFF (skip) START TRUE false ON-> print (just turned on) grab false false ON print (still on) and this false false ON print (still on) END false TRUE ->OFF print (turning off now) ignore false - OFF (skip) Key insight: both the START and END lines are printed. The flip happens AFTER the match. ============================================================================ PART 4: LOG FILE PARSING ------------------------ Extract a specific time range from logs: while (<$log>) { print if m~\[10:00:~ .. m~\[11:00:~; } Grabs everything from 10:00 to 11:00. Extract a specific request: while (<$log>) { print if m~BEGIN Request #1234~ .. m~END Request #1234~; } Perfect for debugging specific transactions. ============================================================================ PART 5: LINE NUMBER RANGES -------------------------- Flip-flop works with line numbers too: while () { print if 3 .. 7; } Prints lines 3 through 7. How? In scalar context, bare numbers compare against $. (the line number). So 3 .. 7 means "line 3 through line 7." perl -ne 'print if 5 .. 10' file.txt One-liner: print lines 5-10. ============================================================================ PART 6: THE THREE-DOT VERSION ----------------------------- There's also ...: while () { print if m~START~ ... m~END~; } Difference: with ..., the right side isn't checked on the same line that triggered the left side. .. Two dots: left and right can match on same line ... Three dots: left and right can't match on same line Usually doesn't matter. But for edge cases: # Line says "START something END" m~START~ .. m~END~ # Flips on AND off on same line m~START~ ... m~END~ # Flips on, stays on until NEXT END ============================================================================ PART 7: MULTIPLE RANGES ----------------------- The flip-flop resets when it turns off, so you can catch multiple ranges: while () { print if m~BEGIN~ .. m~END~; } __DATA__ noise BEGIN first block END more noise BEGIN second block END final noise Output: BEGIN first block END BEGIN second block END Both blocks are captured. ============================================================================ PART 8: CONFIG FILE PARSING --------------------------- Extract a section from a config: while (<$config>) { print if m~\[database\]~ .. m~^\[~; } From [database] until the next [section]. Gotcha: this also prints the next section header. To exclude it: while (<$config>) { if (m~\[database\]~ .. m~^\[~) { print unless m~^\[~ and not m~\[database\]~; } } Or process into a hash: my %config; my $section; while (<$config>) { $section = $1 if m~^\[(\w+)\]~; $config{$section}{$1} = $2 if m~^(\w+)=(.*)~; } ============================================================================ PART 9: ONE-LINERS ------------------ Print between patterns: perl -ne 'print if /START/ .. /END/' file.txt Print lines 10-20: perl -ne 'print if 10 .. 20' file.txt Print from pattern to end of file: perl -ne 'print if /START/ .. eof' file.txt Print from start to pattern: perl -ne 'print if 1 .. /END/' file.txt ============================================================================ PART 10: STATEFUL WARNING ------------------------- The flip-flop has PERSISTENT state. This can bite you: sub extract_section { my $file = shift; open my $fh, '<', $file or die; while (<$fh>) { print if m~START~ .. m~END~; } } extract_section('file1.txt'); extract_section('file2.txt'); # STATE CARRIES OVER! If file1.txt ends without hitting END, the flip-flop stays ON for file2.txt. Fix: reset state by reaching END, or use a different approach. ============================================================================ PART 11: THE NAME ----------------- Flip-flop comes from electronics. A flip-flop circuit has two stable states and switches between them based on input signals. ON ----+ +---- ON | | v v [TRIGGER] ^ ^ | | OFF ----+ +---- OFF Perl's .. does the same thing: flips on with one condition, flops off with another. ============================================================================ PART 12: PRACTICAL EXAMPLE -------------------------- Extract all function definitions from Perl code: while (<$source>) { print if m~^sub \w+~ .. m~^}~; } From "sub name" to the closing brace. Extract error blocks from logs: while (<$log>) { print if m~ERROR~ .. m~^$~; } From ERROR to the next blank line (assuming stack traces are followed by blank lines). ============================================================================ .. / \ ON OFF \ / \/ [MEMORY] Stateful range matching since Perl 1 ============================================================================ japh.codes