Skip to content
academia.sh

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.

Start typing to search.

↑↓ Esc navigate · open · close