Desk Clarity

A field guide to everyday digital problems.

Developer tools / Practical guide

A Bash Heredoc Expands Template Variables Too Early: Quote the Delimiter

Quote the heredoc delimiter when writing literal shell or template text.

By ON Lab2 min readSources checked November 10, 2026 (KST)

Quote the heredoc delimiter when writing literal shell or template text. An unquoted delimiter allows the current shell to expand the body before the receiving command sees it.

Reproduce in an empty temporary directory

This isolated example creates only fixture data in its working directory or runs local JavaScript. Run it separately from your project so the named files cannot overwrite real work.

sample_name=outer
cat > expanded.txt <<EOF
name=$sample_name
EOF
cat > literal.txt <<'EOF'
name=$sample_name
EOF
printf 'expanded: '; cat expanded.txt
printf 'literal: '; cat literal.txt
grep -F 'name=$sample_name' literal.txt

Observed local output

expanded: name=outer
literal: name=$sample_name
name=$sample_name

What to compare

The first output contains outer; the second contains the literal dollar expression. The destination command is cat in both cases. The difference comes from how Bash prepares the heredoc body. Quoting around a variable inside an unquoted heredoc body does not supply the same boundary as quoting the delimiter, because the body has its own expansion rules.

Choose the next action

For a generated script that should expand variables only when it later runs, use a quoted delimiter and review the resulting file before execution. If the template needs a small number of known substitutions now, make those substitutions through an explicit template mechanism rather than leaving every dollar expression active. Test a fixture containing both a current value and a future placeholder. Keep command substitution text literal when it is intended for the generated file.

Limits and verification

The closing delimiter must be recognized on its own line. The <<- variant strips leading tab characters, not arbitrary indentation spaces. This fixture does not run the generated content; it compares the files as data. That distinction matters for SQL, remote commands and configuration snippets where early expansion can silently change meaning. Use a format-aware library when the output requires escaping beyond literal text.

Related: Bash Prefix Assignment Changes the Child Environment, Not Earlier Argument Expansion.

Isolated example executed on macOS on November 10, 2026 (KST), using Bash 3.2, Python 3 for the byte fixture, and Node v26.5.0. Results describe the example, not your application or every runtime version.

Sources & further reading