Linux Basic — Here-Documents

1 minute read

Here-Documents (<<EOF)

A here-document feeds a block of literal text as standard input to a command. It replaces fragile echo -e "line1\nline2" chains and is the canonical way to embed SQL, config templates, or multi-line emails inside a script.


Basic Syntax

cat <<EOF
Hello, $USER
Today is $(date +%F).
EOF

The delimiter (EOF) is your choice — it just has to appear unindented on a line by itself to end the heredoc. Variables ($USER) and command substitution ($(date)) are expanded by default.


Disable Expansion: Quote the Delimiter

cat <<'EOF'
Literal $USER, no expansion.
Backticks `like this` stay literal too.
EOF

Quoting the opening delimiter ('EOF' or "EOF") treats the body as raw text. Use this for scripts within scripts, AWK programs, or anything containing $ you don’t want expanded.


Strip Leading Tabs: <<-

<<-EOF strips leading tab characters (not spaces) from each line — letting you indent the body to match surrounding code:

if true; then
    cat <<-EOF
	this line has leading tabs
	they will be stripped
	EOF
fi

Only tabs are stripped. Mixing tabs and spaces silently breaks this.


Heredocs to Commands Other Than cat

Anything that reads stdin works:

# Send mail
mail -s "Report" ops@example.com <<EOF
Run completed at $(date).
EOF

# Pipe SQL to psql
psql -d mydb <<SQL
SELECT count(*) FROM users WHERE active = true;
SQL

# Build a config file
sudo tee /etc/myapp.conf >/dev/null <<EOF
listen_port = 8080
log_level   = info
EOF

Capture a Heredoc into a Variable

template=$(cat <<EOF
host: $HOSTNAME
user: $USER
EOF
)
echo "$template"

Common Pitfalls

Pitfall Fix
Closing EOF is indented with spaces Must be flush-left (or use <<- with tabs only)
Trailing whitespace after opening <<EOF Bash treats the literal delimiter strictly — clean line endings
Variable expansion happens when you don’t want it Quote the delimiter: <<'EOF'
Heredoc inside a pipeline runs in a subshell Variables set inside aren’t visible after

Quick Reference

Form Expansion Indent stripping
<<EOF yes none
<<'EOF' no none
<<-EOF yes leading tabs
<<-'EOF' no leading tabs

Next → Here-Strings

Updated: