Heredoc Basics - Embedding Multiline Strings in Shell Scripts
What Is a Heredoc?
A heredoc (here document) lets you embed multiline strings directly in a shell script. It starts with <<DELIMITER and ends when the delimiter appears alone on a line. Compared to chaining multiple echo calls, heredocs are far more readable and are widely used for generating config files, running SQL queries, and feeding commands over SSH.
Basic Syntax
Write <<DELIMITER, then the body text, then the closing delimiter on its own line.
cat <<EOF First line of text Second line of text Third line of text EOF
First line of text Second line of text Third line of text
The delimiter can be any string — EOF, END, HEREDOC — but EOF (End Of File) is the conventional choice.
The closing delimiter must start at column zero. Any leading spaces or tabs will prevent it from being recognized, causing the script to hang waiting for input.
Variable Expansion Is On by Default
With <<EOF (unquoted), variables and command substitutions inside the heredoc are expanded by the shell before output.
NAME="Alice" TODAY=$(date +%Y-%m-%d) cat <<EOF Hello, $NAME Today is $TODAY EOF
Hello, Alice Today is 2026-06-01
To disable expansion, quote the delimiter with single quotes.
NAME="Alice" cat <<'EOF' Hello, $NAME $(date) is not expanded EOF
Hello, $NAME $(date) is not expanded
When to use which
- Embed shell variables in the output →
<<EOF(default) - Output a template or code snippet verbatim →
<<'EOF'
Strip Leading Tabs with <<-
A standard heredoc preserves all leading whitespace. With <<-, only leading tab characters are stripped. This lets you indent the heredoc body to match the surrounding code without the indentation appearing in the output.
if true; then cat <<-EOF Indented block Tabs are stripped from output EOF fi
Indented block Tabs are stripped from output
Only tab characters (\t) are stripped — not spaces. If your editor uses space indentation, you need to convert those lines to tabs for <<- to work.
Writing to Files
Combine a heredoc with redirection to generate config files directly from a script.
cat <<EOF > /tmp/config.conf host=localhost port=5432 dbname=mydb EOF
To append instead of overwrite, use >>:
cat <<EOF >> /tmp/config.conf user=admin password=secret EOF
For files that need root permissions, pipe through sudo tee. Using sudo cat <<EOF > /root/file does not work because the shell opens the redirect target before sudo takes effect.
cat <<EOF | sudo tee /etc/myapp/config.conf host=localhost port=5432 EOF
Practical Example — Running Commands Over SSH
ssh user@server <<EOF echo "Starting deployment" cd /var/www/myapp git pull origin main systemctl restart myapp EOF
Use <<EOF when you want variables expanded locally before the string is sent to the remote shell. Use <<'EOF' when you want the remote shell to expand them instead.
Practical Example — Feeding Queries to MySQL
mysql -u root -p mydb <<EOF SELECT id, name FROM users WHERE active = 1; UPDATE users SET last_login = NOW() WHERE id = 42; EOF
This pattern is common in initialization scripts and batch jobs that need to run multiple SQL statements in a single session.
Common Pitfalls
No spaces or tabs before the closing delimiter
# Wrong: the closing EOF has leading spaces and is never recognized cat <<EOF content EOF EOF
The closing delimiter must begin at column zero (or use <<- with tabs only).
Escaping dollar signs for literal output
With <<EOF (expansion enabled), prefix $ with a backslash to output it literally.
cat <<EOF Home directory variable: \$HOME Actual value: $HOME EOF
Home directory variable: $HOME Actual value: /home/alice
Capturing a heredoc into a variable
To store a heredoc in a variable rather than piping it to a command, use command substitution with $().
TEXT=$(cat <<EOF multiline text stored in a variable EOF ) echo "$TEXT"