<!-- category: hidden-gems -->
$\ and $, - Invisible Modifiers That Change print
You are reading someone else's code. Everyprint statement outputs
a newline at the end. But none of them say \n. There is no say.
No explicit newline anywhere.
Somewhere, hidden at the top of the file, there is this:
And now every$\ = "\n";
print in the entire program silently appends a
newline. You just stared at the output for twenty minutes wondering
where the newlines were coming from. Two characters in a dark corner
of the file changed the behavior of a builtin.
Welcome to Perl's output separator variables. They are powerful, invisible, and absolutely maddening when you do not know they exist.
Part 1: $\ THE OUTPUT RECORD SEPARATOR
The special variable$\ (also known as $OUTPUT_RECORD_SEPARATOR
if you use English) is appended to the end of every print
statement. By default, it is undef, which means print appends
nothing.
You just turned$\ = "\n"; print "hello"; # outputs: hello\n print "world"; # outputs: world\n print "done"; # outputs: done\n
print into say. Every single print in your
program now adds a newline, whether you asked for one or not.
Set it to something else and things get interesting:
$\ = "\n---\n"; print "section one"; print "section two";
Every print gets a section divider appended. No changes to any print statement. The separator is invisible at the call site.section one --- section two ---
Part 2: $, THE OUTPUT FIELD SEPARATOR
The special variable$, (also known as $OUTPUT_FIELD_SEPARATOR)
is inserted between the arguments of print. By default, it is
undef, so arguments are concatenated with nothing between them.
Setprint "a", "b", "c"; # outputs: abc
$, and you get separation:
Every comma-separated argument to$, = " | "; print "a", "b", "c"; # outputs: a | b | c
print now gets " | " inserted
between them. Print just became a join-and-output function.
Instant CSV. Sort of. (Real CSV needs quoting. But for quick output, this is faster than building strings.)$, = ","; print "name", "age", "city"; # outputs: name,age,city
Part 3: COMBINING BOTH
Use them together for formatted output:$, = "\t"; # tab between fields $\ = "\n"; # newline after each print print "Name", "Age", "City"; print "Alice", 30, "Toronto"; print "Bob", 25, "Montreal";
Tab-separated output with automatic newlines. EveryName Age City Alice 30 Toronto Bob 25 Montreal
print is now
a row emitter. Clean. Concise. And completely invisible to anyone
reading just the print statements.
This pattern is popular in one-liners:
Extract columns 0, 2, and 4 from whitespace-delimited data, output as tab-separated. The$ perl -lane '$, = "\t"; $\ = "\n"; print @F[0,2,4]' data.txt
$, and $\ variables do all the formatting.
Part 4: TURNING PRINT INTO SAY
The most common use of$\ is the newline trick:
This predates$\ = "\n";
say by decades. Before Perl 5.10 added say in
2007, this was how you avoided typing \n at the end of every
print statement. Old Perl code is full of it.
Modern code should just use# Pre-5.10 code $\ = "\n"; print "Starting process"; print "Step 1 complete"; print "Step 2 complete"; print "Done";
say. It does the same thing without
the invisible global state. But understanding $\ matters because
you will encounter it in legacy code, and because it can do things
say cannot. Like appending something other than a newline.
$\ = " [END]\n"; print "First record"; print "Second record";
First record [END] Second record [END]
say always appends \n. $\ appends whatever you want.
Part 5: THE CSV TRICK
Quick and dirty CSV output using$,:
#!/usr/bin/env perl use strict; use warnings; $, = ","; $\ = "\n"; print "id", "name", "score"; my @students = ( [1, "Alice", 95], [2, "Bob", 87], [3, "Carol", 92], ); for my $s (@students) { print @$s; }
Eachid,name,score 1,Alice,95 2,Bob,87 3,Carol,92
print @$s automatically comma-separates the array elements
and appends a newline. No join, no string building, no explicit
formatting. The variables handle it.
Is this real CSV? No. Real CSV needs to handle commas inside fields,
quoting, and escaping. Use Text::CSV for that. But for quick data
dumps where you control the content, this is fast and clean.
Part 6: SCOPING WITH LOCAL
Here is the critical safety tip.$\ and $, are global variables.
Setting them affects every print in your program. Every module you
call. Every library function that uses print. Everything.
The fix is$, = "|"; $\ = "\n"; print "a", "b"; # a|b\n (expected) some_library_function(); # surprise! its print statements # now use | separators too
local:
The{ local $, = "|"; local $\ = "\n"; print "a", "b"; # a|b\n } # $, and $\ are restored to their previous values here print "a", "b"; # ab (no separator, no newline)
local keyword dynamically scopes the variable. Inside the
block, the new value is in effect. Outside, the old value is
restored. Even if the block calls other functions, those functions
see the localized value. But when the block ends, everything goes
back to normal.
Always use local with these variables. Setting them globally
is asking for bizarre bugs in any non-trivial program.
Part 7: THE DEBUGGING NIGHTMARE
Someone sets$\ at the top of a module. You use that module.
Now your print statements append things you did not ask for.
package WeirdModule; $\ = "!!!\n"; # global side effect # ... rest of module ... 1;
You did not setuse WeirdModule; print "hello"; # outputs: hello!!!\n
$\. You did not ask for exclamation marks. But
there they are, because a module you imported set a global variable
as a side effect. This is why responsible Perl code localizes these
variables and why code reviews should flag any bare $\ = ...
outside a local block.
Debugging this is miserable. The symptom (weird output) is far
from the cause (a global variable set in another file). If you
ever see mysterious extra characters in your output, check $\
first:
# diagnostic use Data::Dumper; say Dumper($\); # see what $\ is set to say Dumper($,); # and $,
Part 8: USE IN ONE-LINERS
These variables truly shine in command-line one-liners where brevity is everything:In a one-liner, you do not care about global state. The program starts, runs, and dies. No modules are affected. No long-running process suffers side effects. The global nature of# Print every line with a line number $ perl -ne '$\ = "\n"; print "$. $_"' file.txt # Join fields with pipes $ perl -ane '$, = " | "; $\ = "\n"; print @F' data.txt # TSV to CSV conversion $ perl -F'\t' -ane '$, = ","; $\ = "\n"; print @F' input.tsv > output.csv
$, and $\
is a feature here, not a bug.
The -l flag is related. It sets $\ to the input record
separator (usually \n) automatically:
The$ perl -lne 'print "PREFIX: $_"' file.txt
-l flag is doing $\ = "\n" behind the scenes. It also
chomps each input line. A tiny flag doing two invisible things.
Part 9: THE ENGLISH MODULE NAMES
If$\ and $, look like line noise to you, the English module
provides readable names:
Same variables, human-readable names. Theuse English qw(-no_match_vars); $OUTPUT_RECORD_SEPARATOR = "\n"; $OUTPUT_FIELD_SEPARATOR = ", "; print "one", "two", "three"; # one, two, three\n
-no_match_vars flag is
important because use English without it enables $PREMATCH,
$MATCH, and $POSTMATCH, which have a global performance penalty.
More on that in another post.
The English names are great for documentation and learning. In production code, most Perl programmers use the short names because they are faster to type and universally recognized (among Perl programmers, anyway).
Part 10: WHEN TO USE THEM
.--. |o_o | "print does what I tell it. |:_/ | Even the things I told it // \ \ three hundred lines ago." (| | ) /'\_ _/`\ \___)=(___/
$\ and $, are power tools. They let you change the behavior of
print globally, which means you can format output without touching
any print statement. One assignment at the top and every print in
the program obeys.
That power is also the danger. Global state is global state. One
careless assignment and every module, library, and function that
uses print is affected. Always scope with local. Always.
For one-liners, use them freely. They save keystrokes and make complex text transformations concise.
For scripts, use them with local in a limited scope. Or just use
say and join explicitly. The explicitness of say join(",", @fields)
beats the cleverness of setting $, and hoping nobody else touches
it.
For modules, never set them without localizing. A module that sets global output separators is a module that gets uninstalled.
Know they exist. Know what they do. Know how to spot them in someone else's code. That is the real value. Not using them yourself, but recognizing them when they are silently changing your output from somewhere you did not expect.
perl.gg