Lesson 04 / 24
Here Documents and Here Strings
Input embedded in a script, the effect of quoting the delimiter on expansion, indentation removal, and single-line input notation.
Contents
The previous three lessons showed two sources of input: the keyboard and a file. Scripts need a third source as well — fixed content embedded in the script’s own text. A configuration template, a multi-line usage message, test data, and a database query all belong to this class.
Keeping this content in a separate file makes the script unable to travel on
its own. Writing every line with a separate echo requires managing line
endings, quotes, and spaces by hand. The shell defines two notations for this
job.
The Here Document
A here document begins by writing a delimiter after the <<
operator; the text up to the line where the delimiter appears alone becomes
the command’s standard input.
code=404 cat <<END Requested code: $code END
Requested code: 404
The delimiter’s name is free; a string that will not appear in the content is
chosen. EOF is a common choice, but it is not mandatory — this course will
use END.
Two rules are strict. No character other than the delimiter itself can be on the delimiter’s line; even a single trailing space disqualifies the line from being the delimiter, and the shell keeps reading to the end of the file and reports an error. Second, the delimiter’s line cannot be indented — an exception to this is covered below.
Expansion and Quoting the Delimiter
A here document’s content is, by default, processed as if it were inside double quotes: variable expansion, command substitution, and arithmetic expansion are applied.
name="report" cat <<END Log: $name.log Line count: $(grep -c '' access.log) Arithmetic: 5 * 3 = $((5 * 3)) END
Log: report.log Line count: 30 Arithmetic: 5 * 3 = 15
If the delimiter is quoted, no expansion is done at all; the text passes through literally. Quoting can be done with single quotes, double quotes, or a backslash; all three give the same result.
cat <<'END' Log: $name.log Command: $(pwd) Arithmetic: $((5 * 3)) END
Log: $name.log Command: $(pwd) Arithmetic: $((5 * 3))
This distinction is not a stylistic flourish but a condition for correctness.
When embedding another script’s source code, a shell configuration, or any
template containing $ into a script, if the delimiter is not quoted, the
$ signs in the embedded text get expanded in the outer script and the
content silently breaks. As a rule: the delimiter is left unquoted only if
expansion is deliberately wanted; otherwise it is quoted.
Removing Indentation
A here document does not follow the surrounding code’s indentation when it is
inside an if or for body; because the content is transferred literally,
the indentation enters the text too. The <<- form partially solves this
problem: it strips the leading tab characters from the content lines and
the delimiter line.
if true; then cat <<-END indented line second line END fi
indented line second line
The restriction is plain: only the tab character is stripped, not spaces. In
a file indented with spaces, <<- does nothing at all. This is one of the
corners of the shell language that has been kept portable but stayed
impractical; if your style rules require space indentation, leaving the here
document unindented is less surprising.
The Here String
A here string is a shorthand for single-line input. The value to the
right of the <<< operator becomes the command’s standard input, with a
newline appended to its end.
name="report" tr 'a-z' 'A-Z' <<< "$name ready"
REPORT READY
The appended newline is real and it counts:
wc -c <<< "abc" printf '%s' "abc" | wc -c
4
3
This difference matters in jobs that count length or compare byte for byte.
If you do not want the newline, use a printf pipeline instead of a here
string.
The most common use of a here string is splitting a string into its fields:
read -r address code <<< "10.0.0.12 200" echo "address=$address code=$code"
address=10.0.0.12 code=200
Doing the same job with a pipeline would fall into the previous lesson’s
subshell trap: in the notation echo ... | read, read runs in a subshell
and the variables it assigns are lost. A here string is redirection, not a
pipe; the command stays in the parent shell. <<< is a bash extension,
absent from the POSIX shell language.
In a Pipeline and With Redirection
A here document is also an ordinary input redirection; it can be used together with the command’s other redirections and with a pipeline. Redirections are collected at the end of the command line, while the content starts from the lines following the command:
sort <<'END' | head -2 gamma alpha beta END
alpha beta
Writing the output to a target is also specified on the same line. This is the most direct way to dump fixed content into a file:
cat > mini.log <<'END' 10.0.0.1 GET /a 200 10.0.0.2 GET /b 404 END
wc -l mini.log
2 mini.log
Reproducing the Log
This course’s access.log file is not downloaded from an external source; it
is produced by a script. This way, every reader works with the same data, and
results become directly comparable.
The make-data.sh script below writes the log from scratch. The delimiter is
quoted: even though $ does not appear in the content, not leaving data text
open to expansion is the correct default.
cat > make-data.sh <<'SCRIPT_END' cat > access.log <<'END' 10.0.0.12 - - [07/Feb/2024:09:12:44 +0000] "GET /index.html HTTP/1.1" 200 5120 10.0.0.31 - - [07/Feb/2024:09:12:51 +0000] "GET /static/style.css HTTP/1.1" 200 2048 END SCRIPT_END
The outer delimiter (SCRIPT_END) and the inner one (END) were chosen
differently. If they were the same, the outer document would end at the
first END line, and the rest of the text would be interpreted as commands.
When writing nested here documents, the delimiters must be different.
The fragment above contains the first two of the thirty lines from the start
of the lesson; the full data set was given in the Standard Input, Output, and
Error lesson. Build your own make-data.sh file with all thirty lines; in
later lessons, the log will be recoverable with a single command if it gets
corrupted.
Which One, When
| Notation | Input | Expansion | Typical use |
|---|---|---|---|
<< END |
multi-line | applied | template, formatted report header |
<< 'END' |
multi-line | not applied | embedded source code, fixed data |
<<< |
single-line | applied | splitting a string into fields, filtering one line |
As a fourth option, a printf pipeline can always be used and is fully
portable; in exchange, it requires managing newlines by hand.
Summary
- A here document begins with
<<and makes the text up to the line where the delimiter appears alone the command’s standard input. - If the delimiter is not quoted, the content is expanded by the double-quote rule; if quoted, it passes through literally. Quoting is mandatory for embedded source code.
- The
<<-form strips only tab indentation; it has no effect on space indentation. - A here string gives single-line input and appends a newline to its end; unlike a pipeline, it does not push the command into a subshell.
- In nested here documents, the delimiters must be chosen differently.
Next Step
Every example in this topic assumed the commands succeeded. In reality,
grep reports a result when it finds no match, cut when it cannot open the
file, or sort when it finds no disk space. The next lesson defines that
report — the exit status: why zero means success, how the && and ||
operators tie to this code, and the code ranges the shell reserves for
itself.
To keep your progress and take notes, Log in
My notes
Log in to take notes.