Record Separator
This page explains how to control Perl's input record separator, the$ variable, so you can read files by lines, paragraphs, byte counts, or custom strings. Most developers never touch it, but adjusting it changes how Perl divides your input.
By default the value is a newline, which is why the diamond operator reads one line at a time. You change the value and you change how Perl sees the world.
One variable handles four completely different behaviors.$/= undef; # Slurp entire file $/= ""; # Paragraph mode $/= \1024; # Read 1024 bytes at a time $/= "END"; # Read until "END"
Part 1: SLURP MODE
That's it. The whole file lands in one scalar without a loop.$/ = undef; my $entire_file = <>;
In a one-liner:
Theperl -0777 -ne 'print length' file.txt
-0777 flag sets $ to undef. The 777 is octal for "nothing" because no character matches that code, so Perl interprets it as "no separator at all."
You would use this for multi-line regex matches:
Without slurp mode that regex cannot cross line boundaries.perl -0777 -pe 's/START.*?END/REPLACED/gs' file.txt
Part 2: PARAGRAPH MODE
An empty string tells Perl to read paragraph by paragraph, which means chunks of text separated by blank lines.$/ = "";
Theperl -00 -ne 'print if /error/i' logfile.txt
-00 flag enables paragraph mode. Each $_ holds a full paragraph, so if any line inside that paragraph contains "error" you can print the whole block.
You get context around your matches for free.
.--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/
Part 3: FIXED-WIDTH RECORDS
Here is where the variable gets interesting. You set$ to a reference to a number:
Now the diamond operator reads exactly 1024 bytes at a time instead of lines.$/ = \1024;
That reads 16 bytes per iteration, which fits binary files with fixed record sizes perfectly.perl -e '$/ = \16; print "<$_>\n" while <>' binary.dat
Database dumps, network packet captures, and old mainframe files with 80-character records all fit this pattern.
Part 4: CUSTOM DELIMITERS
Perl now reads until it hits the literal string "END".$/ = "END";
That gives you ghetto XML parsing, since eachperl -e '$/ = "</record>"; print "---\n$_" while <>' data.xml
$_ contains everything up to and including the </record> tag.
It is not proper parsing, but it works when you need something quick and dirty on a server that lacks XML libraries.
Part 5: THE SWITCHES
Command line shortcuts:TheSWITCH EQUIVALENT MEANING ------ ----------------- --------------------------- -0 $/ = "\0" Null-separated (find -print0) -00 $/ = "" Paragraph mode -0777 $/ = undef Slurp mode -0x $/ = chr(x) Octal character code
-0 flag without a number means null byte, which pairs perfectly with find -print0 and xargs -0:
You get null-separated filenames, which removes issues with spaces or newlines in paths.find . -name "*.log" -print0 | perl -0 -ne 'print if -M $_ > 7'
Part 6: BINARY CHUNKING
Processing a large binary file in chunks:That gives you a progress indicator for large file processing, since each read is exactly 4096 bytes except possibly the last one.perl -e ' $/ = \4096; my $total = 0; while (<>) { $total += length; print STDERR "Read $total bytes\r"; } print STDERR "\nDone: $total bytes\n"; ' hugefile.bin
Part 7: RECORD-ORIENTED DATA
Old-school fixed-width data:Twenty bytes per record lets# Data file: 10-char name, 3-char age, 7-char salary # JohnSmith 034 0085000 # JaneDoe 028 0092000 perl -e ' $/ = \20; while (<>) { my ($name, $age, $salary) = unpack("A10 A3 A7", $_); print "$name: $age years, \$$salary\n"; } ' payroll.dat
unpack pull out the fields directly, so you skip parsing entirely when the structure stays fixed.
Part 8: MULTIPLE FILES
When you process multiple files,$ persists across them:
Each file gets slurped separately, andperl -0777 -ne 'print "$ARGV: " . length . " bytes\n"' *.txt
$ARGV tells you which file you are reading.
You should watch the line counter though, since $. does not reset between files unless you explicitly close ARGV:
The butterfly operator handles the reset for you.perl -00 -ne 'print "$ARGV:$.: $_" }{ close ARGV' *.txt
Part 9: COMBINING WITH OUTPUT
There is also$\, which is the output record separator:
Every print statement automatically appends "---" between paragraphs.$/ = ""; # Read paragraphs $\ = "\n---\n"; # Print separator after each output while (<>) { print if /important/; }
In one-liner form:
Theperl -00 -l -ne 'print if /error/' logfile.txt
-l flag sets $\ to match $ roughly, so output gets auto-newlined.
Part 10: THE MAGIC TABLE
Quick reference:$/ READS ------------ ------------------------------------ "\n" One line (default) undef Entire file "" Paragraph (text between blank lines) \N Exactly N bytes "STRING" Until STRING appears "\0" Until null byte SWITCH EQUIVALENT ------ ---------- -0 $/ = "\0" -00 $/ = "" -0777 $/ = undef -0NNN $/ = chr(oct("NNN"))
Part 11: REAL WORLD
Extracting functions from C code:Finding the longest line in a file:perl -0777 -ne 'print "$1\n" while /^(\w+ \w+\([^)]*\))\s*{/gm' *.c
Processing CSV with embedded newlines (fields quoted):perl -0777 -ne 'print length($1) while /(.+)/g' file.txt | sort -n | tail -1
The record separator is one of Perl's quiet superpowers, since most languages only give you lines while Perl gives you whatever you want.perl -0777 -ne ' while (/"([^"]*)"/) { my $clean = $1; $clean =~ s/\n/\\n/g; s/"[^"]*"/"$clean"/; } print ' data.csv
perl.gg$/ / \ / \ | ?? | \ / \ / \/ Define your own reality