erdivartanovich

Shell heredocs: one mental model, four tools

Erdiansyah

2026-07-14

You know that move where you paste a whole config into a Slack code block, so nobody has to type it line by line? A heredoc is that move, but for your shell.

It hands a multi-line block of text to a command in one go:

command <<EOF
line one
line two
EOF

EOF is just a marker meaning “block ends here.” You could use BANANA. Please don’t.

The one decision that matters: template or mail-merge?

Tiny quotes around the marker completely change what gets delivered. This is the part everyone learns the hard way, usually in production.

Writing a config that contains real dollar signs? Quote the marker. Want to inject your current git tag into the block? Don’t.

Tool 1: write a file with zero middlemen

Most snippets online use cat to pipe the block into a file. Plot twist: you don’t need a command at all. The shell does redirection natively:

>> ~/.zshrc <<'EOF'
alias gpo='git push origin'
alias ga='git add .'
EOF

Why the command-free version wins:

Tool 2: run your runbook in one shot

Point the block at an interpreter instead of a file, and every line gets executed. It’s the difference between emailing someone a checklist and having them actually do it:

ssh user@server <<'EOF'
cd /var/www && git pull
systemctl restart nginx
EOF

One SSH connection, the whole deploy checklist, no copy-paste roulette. Works with sudo bash, docker exec -i, even python3.

The quoting rule pays double here. Quoted marker = $(df -h) runs on the server. Unquoted = your laptop fills it in before sending. Mixing those up is a classic “why is disk usage identical on every host” bug.

Tool 3: keep it indented

Normally the closing marker must sit flush-left, which ruins your nicely indented function. Write <<-EOF and the shell strips leading tabs (tabs only — spaces don’t count, because of course they don’t).

Tool 4: the one-liner

For a single value, skip the ceremony. Three arrows — a herestring — feed one string to a command:

tr 'a-z' 'A-Z' <<< "hello world"

FAQ

What happens if I forget the closing marker? The shell sits there waiting, like a PR with no reviewer. Type the marker on its own line and it snaps out of it.

Quoted or unquoted — what’s the safe default? Quoted (<<'EOF'). Verbatim delivery never surprises you. Unquote only when you deliberately want local values baked in.

Does this work in zsh too, or just bash? Both, plus basically every POSIX shell. Heredocs are older than most codebases you’ll ever touch.