Filehandle and printing state#
Seven variables that drive Perl’s I/O. They are globals — setting $/ in one routine changes how every other routine in the same process reads from a filehandle. The discipline that keeps that from being a disaster is local-isation: every change to a variable on this page should be inside a local block unless the program genuinely wants the new value to be permanent.
Variable | Reads as | Default | Used by |
|---|---|---|---|
| input record separator |
|
|
| output record separator |
|
|
| output field separator |
|
|
| list separator |
| array interpolation |
`$ | ` | output autoflush |
|
| current line number |
| last filehandle read |
| subscript separator |
|
|
$/ — the input record separator#
Controls what <$fh> and readline treat as the end of one record. The default "\n" means line-by-line reading; setting it to other values produces other reading modes.
Line mode (the default)#
while (my $line = <$fh>) {
chomp $line; # strips the terminator $/
process($line);
}
chomp removes whatever $/ currently holds — when you change $/, chomp follows it.
Slurp mode#
undef $/ makes <> read the entire file in one go:
my $whole_file = do {
local $/;
<$fh>;
};
The local is essential. Without it, every other piece of code that subsequently reads from a filehandle in the same process will silently slurp.
Paragraph mode#
local $/ = ""; reads paragraph-at-a-time, splitting on runs of two or more newlines:
{
local $/ = "";
while (my $para = <$fh>) {
# $para ends with exactly two newlines (or one at EOF)
process_paragraph($para);
}
}
This is awk’s RS="" behaviour. Leading blank lines at the start of the file are silently skipped.
Fixed-record mode#
local $/ = \N reads N bytes per call:
local $/ = \4096; # binary 4-KiB chunks
while (defined(my $chunk = <$fh>)) {
process($chunk);
}
The reference is to a number, not the number itself. This is the right shape for reading binary data, network packets, or doing bulk copy without scanning for \n.
Multi-character terminators#
local $/ = "\r\n"; # CRLF mode
local $/ = "\nEND-OF-RECORD\n"; # custom marker
The terminator is a literal string, not a regex.
$\ — the output record separator#
Appended to every print call. Default is undef (nothing appended). Setting it changes every subsequent print:
{
local $\ = "\n"; # all prints get a trailing newline
print "first";
print "second";
}
# Output: "first\nsecond\n"
This is the natural fit for -l on the command line — -l sets $\ to \n. In script code, local $\ = "\n" inside a tight reporting loop is occasionally worth it; otherwise say (which forces \n regardless of $\) is the cleaner spelling.
$, — the output field separator#
Inserted between print’s arguments. Default is undef:
print "a", "b", "c"; # "abc"
{
local $, = ", ";
print "a", "b", "c"; # "a, b, c"
}
{
local $, = "\t";
print @items; # tab-separated record
}
$, is per-print, not per-line — it does not separate successive print calls. Combine with $\ for full TSV-style output:
local ($,, $\) = ("\t", "\n");
print 'col1', 'col2', 'col3'; # "col1\tcol2\tcol3\n"
$" — the list separator#
Joins array elements when an array is interpolated into a double-quoted string. Default is " " (single space):
my @arr = (1, 2, 3);
print "items: @arr\n"; # "items: 1 2 3"
{
local $" = ', ';
print "items: @arr\n"; # "items: 1, 2, 3"
}
{
local $" = "\n ";
print "items:\n @arr\n"; # "items:\n 1\n 2\n 3"
}
$" is only consulted on interpolation. print @arr (no quotes) uses $,, not $".
$| — output autoflush#
Forces a flush after every write on the currently selected filehandle:
$| = 1; # autoflush STDOUT
print "Loading..."; sleep 1; # appears immediately, not at exit
The ”currently selected“ qualification matters. By default, select returns STDOUT, so $| = 1 autoflushes STDOUT. To autoflush a different handle:
my $old = select($log_fh);
$| = 1;
select($old); # restore selection
The slightly nicer modern spelling uses IO::Handle:
$log_fh->autoflush(1);
$| is read-as-boolean: print $| shows 1 or 0.
$. — current line number#
The line number of the last <>-style read. Each filehandle has its own counter; reading from a different handle aliases $. to that handle’s count:
while (<$fh>) {
print "$.: $_"; # numbered listing
}
# After the loop, $. is the total number of lines read.
$. is reset when the filehandle is closed. Reopening (without an explicit close in between) does not reset.
For multi-file iteration via <>, lines are numbered cumulatively across files unless you explicitly close ARGV at each file boundary — see eof for the standard close ARGV if eof; idiom.
$; — the subscript separator#
Used to fake multi-dimensional hashes — a deeply legacy feature preserved for backward compatibility:
$h{$x, $y} = $value; # equivalent to $h{"$x\034$y"}
The default value is "\034" (FS, ”file separator“, an obscure ASCII control byte). Real multi-dimensional structures should use nested hash refs ($h{$x}{$y}); $; exists because Perl 4 had no proper references.
When you actually localise these#
The most common shape, by far:
sub slurp_text {
my ($path) = @_;
open my $fh, '<', $path or die "open $path: $!";
local $/; # slurp inside this sub only
return <$fh>;
}
sub paragraphs_of {
my ($path) = @_;
open my $fh, '<', $path or die "open $path: $!";
local $/ = "";
return <$fh>; # list context: list of paragraphs
}
sub csv_print {
my (@row) = @_;
local ($,, $\) = (",", "\n");
print @row;
}
The cost of forgetting local is invisible at write time and catastrophic at use time — a subroutine that ”just sets $/“ can break unrelated code reading from completely different filehandles. Treat $/, $\, $,, $" as global mode flags: always set them with local.
See also#
chomp— removes whatever$/currently holds; pairs with$/changes.eof— theclose ARGV if eofidiom that makes$.count per-file across<>.select— selects the filehandle that$|and the unprefixed$,/$\apply to.IO::Handle— providesautoflush,output_record_separator, etc. as method calls.local— the safe way to change any variable on this page.Interpolation — how
$"shapes array expansion inside double-quoted strings.