Perl Part I · Module 4 · Strings and Text
Heredocs for Multi-Line Text
Some strings are several lines long: a usage message, a generated header for an output file, a template with a few values dropped into it. Writing those as a series of print calls or as one string full of backslash-n sequences makes the source hard to read, because the shape of the text on screen stops matching the shape of the output. A heredoc solves that by letting you write the lines as lines, bounded by a marker you choose. It comes in an interpolating and a literal form, following exactly the distinction Chapter 4.1 drew, and in a newer indented form that lets the text sit at the indentation of the code around it rather than jammed against the left margin — which is worth having, and requires a Perl version worth checking for.
Foundation12 min readPerlPart IFundamentalsStringsFormatting
Part I · Module 4 · Chapter 4.10 · Heredocs for Multi-Line Text
1. The Problem With Multi-Line Text
A three-line usage message, written the ways you already know:
print "Usage: run.pl [options]\n";
print " -f force\n";
print " -n dry run\n";print "Usage: run.pl [options]\n -f force\n -n dry run\n";Both work. Neither reads like its output. The first repeats print three times; the second has the line structure encoded in escapes, so the shape on screen tells you nothing about the shape of the text.
2. A Heredoc Writes Lines As Lines
use strict;
use warnings;
my $block = "alu";
print <<"END";
Usage: run.pl [options]
block is $block
END
print <<'END';
Literal: $block is not replaced
ENDUsage: run.pl [options]
block is alu
Literal: $block is not replacedThe pieces:
<<"END"says "the string is everything from the next line until a line containing exactlyEND".- The semicolon goes on the
<<line, not after the text. That line is a complete statement; the text that follows is the string's content. - The terminator is yours to name.
ENDis conventional;USAGE,SQLorHEADERare all fine and a descriptive name helps when two heredocs sit near each other. - The newlines are real. Each line of the text ends with a newline in the result, including the last one — which is why the output above needs no
\nanywhere.
The quoting choice is the same as Chapter 4.1's
| Form | Behaves like | Interpolates? |
|---|---|---|
<<"END" | "..." | yes |
<<'END' | '...' | no |
<<END | <<"END" | yes — the bare form interpolates |
The second print above used <<'END' and the output shows $block as four literal characters. That is Chapter 4.1 §1's distinction, applied to a multi-line string, and the same guidance holds: if the text contains a literal $ or @ — a shell snippet, a documentation example, an awk program — use the single-quoted form and nothing needs escaping.
Note the bare <<END interpolates. Writing the quotes explicitly is worth the two characters, because <<"END" and <<'END' differ visibly where <<END requires the reader to recall a default.
3. The Terminator Must Be Alone on Its Line
In the original form, the terminator has to sit at the left margin with nothing else on the line.
use strict;
use warnings;
print <<"END";
text
ENDCan't find string terminator "END" anywhere before EOF at bad-terminator.pl line 4.255The indented END was not recognised as a terminator, so Perl kept reading, ran out of file, and reported the problem as being at the end of the file rather than at the indentation.
That diagnostic shape should look familiar — it is the same anywhere before EOF message that Chapter 4.1 §4's trailing backslash produced, and for the same underlying reason: a string whose end Perl never found. When you see Can't find string terminator, look for an unterminated string rather than for a problem on the line it names.
Which makes the old form awkward inside a function
use strict;
use warnings;
sub usage {
print <<"END";
Usage: run.pl
-f force
END
}
usage();Usage: run.pl
-f forceIt works, and look at the source. The print is indented inside the subroutine, and then the text and terminator are jammed against the left margin, breaking the visual block structure of the file. Every heredoc in a function or a loop looked like that before Perl 5.26.
The sub here is Module 10's syntax, present because the indentation problem only exists inside an indented block.
4. The Indented Form, <<~
use strict;
use warnings;
use v5.26;
sub usage {
print <<~"END";
Usage: run.pl [options]
-f force
-n dry run
END
}
usage();Usage: run.pl [options]
-f force
-n dry runThe tilde says "strip the common leading whitespace". So:
- The source keeps its indentation, and the terminator can be indented with it.
- Perl finds the smallest indentation among the text lines and the terminator, and removes that much from every line.
- Relative indentation survives. The
-fand-nlines were two spaces deeper than the first line in the source, and they are two spaces deeper in the output.
That last point is what makes the feature worth having: you get tidy source and the output indentation you actually wrote.
5. Where Heredocs Earn Their Place
A usage message. The canonical use. One statement, text that looks like its output, and no \n to miscount.
use strict;
use warnings;
use v5.26;
my $script = "run_regression.pl";
print <<~"END";
Usage: $script [options] <block>
-f, --force rerun tests that already passed
-n, --dry-run list what would run and exit
-j N run N simulations at once
Example:
$script -j 8 alu
ENDUsage: run_regression.pl [options] <block>
-f, --force rerun tests that already passed
-n, --dry-run list what would run and exit
-j N run N simulations at once
Example:
run_regression.pl -j 8 aluNote the blank lines came through, the alignment of the option descriptions survived, and $script was interpolated twice. Writing that with \n escapes would be unreadable and easy to get wrong.
A generated file header. When a script writes a file another tool will read, a header saying what produced it saves somebody an afternoon:
use strict;
use warnings;
use v5.26;
my $block = "alu";
my $header = <<~"END";
// Generated by collect_results.pl — do not edit.
// Block: $block
END
print $header;// Generated by collect_results.pl — do not edit.
// Block: aluAssigning a heredoc to a variable with my $header = <<~"END"; works exactly as printing one does — it is an ordinary string expression.
Text containing sigils. A shell command, an awk snippet, a documentation example. <<'END' and no escaping at all:
use strict;
use warnings;
print <<'END';
To check the environment yourself:
echo $PROJ_ROOT
awk '{print $1}' results.txt
ENDTo check the environment yourself:
echo $PROJ_ROOT
awk '{print $1}' results.txtThree dollar signs and two single quotes, none of them escaped. In a double-quoted string $PROJ_ROOT and $1 would interpolate — and under use strict, $PROJ_ROOT would be a compile error, as Chapter 4.2 §2 showed.
6. Why This Matters in Engineering Work
Usage text is the part of a script other people see. A flow script that prints a clear usage message when given no arguments is one somebody else can use without reading it. A heredoc makes that message cheap enough to write properly, which is most of why it gets written at all.
Generated files need provenance. A netlist, a constraint file or a results table produced by a script should say what produced it and from what. Two heredoc lines at the top of every generated file is a small habit that pays off the first time someone finds the file and wonders.
Templates with a few substitutions. A short report preamble, a generated Makefile fragment, a stub module. For a handful of values, an interpolating heredoc is clear and needs no machinery. Beyond that, a real templating approach is better and is out of Part I's scope.
The version question is a real one. <<~ is genuinely nicer and it is also the one thing in this chapter that can fail on a colleague's machine. use v5.26; converts that into a clear compile-time message, and the cost of writing it is one line.
7. Common Mistakes
Putting the semicolon after the text. It belongs on the << line.
Indenting the terminator in the old form. Can't find string terminator "END" anywhere before EOF, and the message names the end of the file.
Putting anything else on the terminator line. It must contain the terminator alone — plus leading whitespace only in the <<~ form.
Using <<~ without checking the Perl version. It needs 5.26 and is a syntax error on older Perls. Declare use v5.26;.
Forgetting that the bare <<END interpolates. Write the quotes explicitly.
Using the interpolating form for text with literal sigils. Under use strict it is often a compile error; otherwise it silently substitutes. Use <<'END'.
Adding \n inside a heredoc out of habit. The line breaks are already real newlines; an added \n gives you a blank line.
8. Exercise
Exercise 1 — both quoting forms. Write a script with my $tool = "dc_shell"; that prints a two-line heredoc with <<"END" and then the same text with <<'END'. Record both outputs and explain the difference in one sentence.
Exercise 2 — produce the confusing error. Write a heredoc whose terminator is indented by four spaces, using the plain <<"END" form. Run perl -c on it and record the exact message and the exit status. Then say why the message names the end of the file.
Exercise 3 — the indented form. Rewrite Exercise 1's first heredoc inside a subroutine using <<~"END", with the text and terminator indented to match the code. Confirm the output has no leading spaces, then add two extra spaces to one line of the text and confirm that line — and only that line — is indented in the output.
Exercise 4 — declare the requirement. Add use v5.26; to your <<~ script. Then explain, in one sentence, what that line buys you on a machine running Perl 5.16 compared with leaving it out.
Exercise 5 — a real usage message. Write a usage message for a script taking a block name, an optional -j N and an optional --dry-run, with an example line at the end. Interpolate the script's own name using $0 from Chapter 2.12. Confirm the alignment of your option descriptions survives into the output.
Exercise 6 — sigils left alone. Using a literal heredoc, print three lines of shell instructions containing $HOME, $1 and a single-quoted awk program. Confirm nothing was interpolated and nothing needed escaping. Then switch to the interpolating form and record what breaks.
Exercise 7 — into a variable. Assign a two-line heredoc to a variable, print its length, and confirm the length accounts for both newlines. Say how many characters the final newline contributes and whether you would want it when writing the string to a file.
9. Summary
- A heredoc holds multi-line text in one statement, with the lines written as lines and real newlines at the end of each.
- The semicolon goes on the
<<line; the text follows it. <<"END"interpolates,<<'END'does not, and bare<<ENDinterpolates — the same distinction as Chapter 4.1.- The terminator must be alone on its line, at the left margin in the original form.
- An indented terminator in the old form gives
Can't find string terminator ... before EOF— the same diagnostic class as an unterminated quoted string. <<~strips the common indentation and preserves the relative indentation, so source and output can both be tidy.<<~requires Perl 5.26. Declareuse v5.26;so an older Perl says so clearly.- Use a literal heredoc for text containing
$or@— shell commands, awk snippets, documentation.
Chapter 4.11 closes the module by returning to comparison and ordering, now for text specifically: why a plain sort puts every uppercase name before every lowercase one, and the short comparator that fixes it.
Where this fits
Part of the Perl curriculum.
