◈ Netsurfer / perl.gg / Record Separator

Record Separator

2025-12-25

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.

$/= undef; # Slurp entire file $/= ""; # Paragraph mode $/= \1024; # Read 1024 bytes at a time $/= "END"; # Read until "END"
One variable handles four completely different behaviors.

Part 1: SLURP MODE

$/ = undef; my $entire_file = <>;
That's it. The whole file lands in one scalar without a loop.

In a one-liner:

perl -0777 -ne 'print length' file.txt
The -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:

perl -0777 -pe 's/START.*?END/REPLACED/gs' file.txt
Without slurp mode that regex cannot cross line boundaries.

Part 2: PARAGRAPH MODE

$/ = "";
An empty string tells Perl to read paragraph by paragraph, which means chunks of text separated by blank lines.
perl -00 -ne 'print if /error/i' logfile.txt
The -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:
$/ = \1024;
Now the diamond operator reads exactly 1024 bytes at a time instead of lines.
perl -e '$/ = \16; print "<$_>\n" while <>' binary.dat
That reads 16 bytes per iteration, which fits binary files with fixed record sizes perfectly.

Database dumps, network packet captures, and old mainframe files with 80-character records all fit this pattern.

Part 4: CUSTOM DELIMITERS

$/ = "END";
Perl now reads until it hits the literal string "END".
perl -e '$/ = "</record>"; print "---\n$_" while <>' data.xml
That gives you ghetto XML parsing, since each $_ 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:
SWITCH EQUIVALENT MEANING ------ ----------------- --------------------------- -0 $/ = "\0" Null-separated (find -print0) -00 $/ = "" Paragraph mode -0777 $/ = undef Slurp mode -0x $/ = chr(x) Octal character code
The -0 flag without a number means null byte, which pairs perfectly with find -print0 and xargs -0:
find . -name "*.log" -print0 | perl -0 -ne 'print if -M $_ > 7'
You get null-separated filenames, which removes issues with spaces or newlines in paths.

Part 6: BINARY CHUNKING

Processing a large binary file in chunks:
perl -e ' $/ = \4096; my $total = 0; while (<>) { $total += length; print STDERR "Read $total bytes\r"; } print STDERR "\nDone: $total bytes\n"; ' hugefile.bin
That gives you a progress indicator for large file processing, since each read is exactly 4096 bytes except possibly the last one.

Part 7: RECORD-ORIENTED DATA

Old-school fixed-width data:
# 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
Twenty bytes per record lets 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:
perl -0777 -ne 'print "$ARGV: " . length . " bytes\n"' *.txt
Each file gets slurped separately, and $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:

perl -00 -ne 'print "$ARGV:$.: $_" }{ close ARGV' *.txt
The butterfly operator handles the reset for you.

Part 9: COMBINING WITH OUTPUT

There is also $\, which is the output record separator:
$/ = ""; # Read paragraphs $\ = "\n---\n"; # Print separator after each output while (<>) { print if /important/; }
Every print statement automatically appends "---" between paragraphs.

In one-liner form:

perl -00 -l -ne 'print if /error/' logfile.txt
The -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:
perl -0777 -ne 'print "$1\n" while /^(\w+ \w+\([^)]*\))\s*{/gm' *.c
Finding the longest line in a file:
perl -0777 -ne 'print length($1) while /(.+)/g' file.txt | sort -n | tail -1
Processing CSV with embedded newlines (fields quoted):
perl -0777 -ne ' while (/"([^"]*)"/) { my $clean = $1; $clean =~ s/\n/\\n/g; s/"[^"]*"/"$clean"/; } print ' data.csv
The record separator is one of Perl's quiet superpowers, since most languages only give you lines while Perl gives you whatever you want.
$/ / \ / \ | ?? | \ / \ / \/ Define your own reality
perl.gg
✓ Document: Doneone-liners / 11.4 KiB▧