Why this matters
Scripts regularly need to produce multi-line text: a configuration file, an email body, a usage message, an SQL query handed to a database client, a small Python program. Writing it with a dozen echo lines is ugly and error-prone (every quote and dollar sign needs thought). A here-document lets you put the text right in the script, as it should look, and feed it to any command's standard input. Its one-line sibling, the here-string, feeds a single string.
The concept
A here-document is a redirection of standard input whose content follows in the script itself:
cat <<EOF
Dear $USER,
your disk is $(df --output=pcent / | tail -1) full.
EOF
<<EOFsays: "standard input for this command is the following lines, up to a line that contains exactlyEOF". The word is your choice;EOFandENDare conventions.- The closing delimiter must be alone on its line: no leading spaces, no trailing spaces, no comment after it.
- The command here is
cat, which copies its input to its output, but any command that reads standard input works:sort <<EOF,python3 - <<'EOF',mail -s subject root <<EOF.
Redirections can be combined as usual. cat > config.ini <<EOF writes the text into a file; cat >> log <<EOF appends it.
Expanding or not
The delimiter's quoting decides how the body is treated:
| Opener | Body behaves like | $var, $(cmd), $((1+2)) | backslash |
|---|---|---|---|
<<EOF | a double-quoted string | expanded | escapes $, backtick, backslash |
<<'EOF' (or "EOF", \EOF) | a single-quoted string | kept literally | kept literally |
Note that even in the expanding form, quotes inside the body are ordinary characters. There is no word splitting and no globbing in a here-document body either, so * and runs of spaces are safe in both forms.
$ name=World
$ cat <<EOF
> Hello, $name. Two plus two is $((2 + 2)). Price: \$5. Quote: "hi"
> EOF
Hello, World. Two plus two is 4. Price: $5. Quote: "hi"
$ cat <<'EOF'
> Hello, $name. Price: \$5.
> EOF
Hello, $name. Price: \$5.
(The > at the start of the lines is bash's continuation prompt, PS2, shown while it waits for the delimiter. You do not type it.)
Indented here-documents: <<-
Inside functions and loops you want the text indented with the code. <<- strips leading tab characters (only tabs, not spaces) from every body line and from the delimiter line:
usage() {
cat <<-EOF
usage: backup.sh [-n] SRC DEST
-n dry run
EOF
}
If your editor turns tabs into spaces, <<- stops working and bash reports warning: here-document at line 2 delimited by end-of-file (wanted 'EOF'). Many people therefore avoid <<- and simply keep here-document bodies unindented.
What happens underneath
Before running the command, bash collects the body (performing the expansions if the delimiter was unquoted) and arranges for it to appear on file descriptor 0. Depending on the size and the bash version it either writes it to a temporary file that it opens and immediately deletes, or writes it into a pipe. Either way the command just sees standard input; cat cannot tell the difference from cat < file. You can see the descriptor from inside:
$ ls -l /proc/self/fd/0 <<EOF
> x
> EOF
lr-x------. 1 learner learner 64 Nov 1 15:02 /proc/self/fd/0 -> pipe:[48211]
Because it is just a redirection, a here-document also works on compound commands:
while read -r host port; do
echo "checking $host:$port"
done <<EOF
web1 80
db1 5432
EOF
Unlike printf ... | while read, this loop runs in the current shell, so variables set inside it survive after done.
Here-strings: <<<
A here-string feeds one word (after expansion) plus a trailing newline to standard input:
$ tr a-z A-Z <<< "hello there"
HELLO THERE
$ read -r year month day <<< "2026 11 01"
$ echo "$month"
11
$ IFS=, read -r -a fields <<< "a,b,,d"
$ declare -p fields
declare -a fields=([0]="a" [1]="b" [2]="" [3]="d")
Here-strings shine when you have a string in a variable and a command that only reads standard input: grep -c x <<< "$text" instead of echo "$text" | grep -c x. Besides being shorter, it avoids a pipeline (and its subshell), so read from a here-string sets variables in the current shell. Always quote the word: <<< $var is not split (here-strings are an exception), but quoting keeps you from thinking about it.
Here-strings are a bash feature (zsh and ksh have them too); POSIX sh does not, as you will see in the portability module.
Worked examples
Generating a file with computed values.
backup_dir=/srv/backup
keep=7
cat > backup.conf <<EOF
# generated by setup.sh on $(date +%F)
BACKUP_DIR=$backup_dir
KEEP=$keep
EOF
Generating a script that must keep its own $ signs. Mixing both needs is a matter of choosing one form and escaping the exceptions. Here most text is literal, so quote the delimiter and add the variable part separately:
{
echo "#!/bin/bash"
echo "target=$target" # expanded now
cat <<'EOF' # literal: expanded later, when cleanup.sh runs
for f in "$target"/*.tmp; do
rm -- "$f"
done
EOF
} > cleanup.sh
The { ...; } group sends the output of all three commands into one file.
Feeding a program.
python3 - "$file" <<'EOF'
import sys, json
print(len(json.load(open(sys.argv[1]))))
EOF
python3 - reads the program from standard input; the quoted delimiter guarantees bash does not touch the Python code.
Common mistakes
- Indented or decorated delimiter.
EOForEOF(trailing space) is not the delimiter, so bash reads to the end of the file and warnshere-document at line 3 delimited by end-of-file (wanted 'EOF'), and everything after it becomes text. - Unexpected expansion. A body with
$PATHor backticks and an unquoted delimiter gets them expanded. If the text is not meant for bash, quote the delimiter. - Spaces with
<<-. Only tabs are stripped. - Forgetting the command.
<<EOFon its own line with no command reads the text and throws it away. - Expecting a here-string without a newline.
<<<always appends\n;wc -c <<< abcprints 4.
Exercises
Two quizzes, a task in the sandbox shell, and a script that mixes expanded and literal dollar signs.
Recap
cmd <<EOF ... EOFfeeds the lines between tocmd's standard input; the delimiter line must match exactly.- Unquoted delimiter: body expands like a double-quoted string (escape with backslash). Quoted delimiter: body is literal.
<<-strips leading tabs; spaces stay.cmd <<< "$string"feeds one string plus a newline; useful withreadand filters.- Underneath, bash connects a temporary file or pipe to file descriptor 0, so the command just reads standard input; redirecting a loop with a here-document keeps the loop in the current shell.
Going further: in man bash (if installed) the section is "Here Documents" under REDIRECTION. Experiment: what does cat <<EOF print for a body line containing \$(echo hi), and for \\$HOME?