Bash Error Guide: 'syntax error: unexpected end of file' — Fix Unclosed Blocks
Fix Bash 'syntax error: unexpected end of file': close every if/do/case/quote/heredoc and strip CRLF line endings that hide the missing terminator.
- #bash
- #automation
- #troubleshooting
- #errors
Stuck on this Bash & Python Automation error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
Bash reports syntax error: unexpected end of file when it reaches the end of the script still waiting for a keyword or delimiter that was never supplied:
./deploy.sh: line 84: syntax error: unexpected end of file
The line number it prints is almost always the last line of the file — not where the mistake is. Bash parses top-to-bottom, opens a compound command (if, for, while, case, { }, a quote, a here-document), and only notices something is missing when it runs out of input. The real bug is an unclosed block far above.
Symptoms
- The error points at the final line (or one past it) even though that line looks fine.
- The script ran yesterday and broke after an edit that added or removed a line in a block.
- Wrapping the whole script or a function in an
if/forreliably reproduces it. bash -n script.sh(no-exec syntax check) reports the same error without running anything.
Common Root Causes
- Missing block terminator — an
ifwithoutfi,for/whilewithoutdone,casewithoutesac, or{without}. - Unclosed here-document —
cat <<EOFwhose closingEOFis misspelled, indented (without<<-), or has trailing whitespace. - Unbalanced quote — an opening
"or'with no partner, so Bash swallows the rest of the file as a string. - A function or subshell
( ... )left open. - CRLF line endings — the closing
fiis reallyfi\r, which Bash doesn’t recognize as the keyword. - A commented-out line that removed a terminator — someone
#-ed out thedoneorfi.
Diagnostic Workflow
First, syntax-check without executing:
bash -n ./deploy.sh
Ask Bash to trace parsing verbosely to see how far it got:
bash -v ./deploy.sh 2>&1 | tail -20
Count block keywords to spot the imbalance:
grep -cE '^\s*if\b' deploy.sh; grep -cE '^\s*fi\b' deploy.sh
grep -cE '\bdo\b' deploy.sh; grep -cE '^\s*done\b' deploy.sh
Rule out CRLF, which hides terminators:
grep -lU $'\r' deploy.sh && echo "has CRLF" # -U = treat as binary
file deploy.sh # "with CRLF line terminators"
Check every here-document delimiter is closed at column zero with no trailing spaces:
grep -nE '<<-?\s*[A-Za-z_]+' deploy.sh
Example Root Cause Analysis
A rollout script failed on line 84 (its last line):
rollout.sh: line 84: syntax error: unexpected end of file
bash -n agreed but still pointed at 84. Counting keywords found the imbalance:
if: 6 fi: 5
One if was missing its fi. The culprit was a here-document whose terminator had been indented during a reformat:
if [ "$MODE" = canary ]; then
cat <<EOF
scaling canary...
EOF # <-- indented; Bash never matches this as the closing EOF
kubectl scale ...
fi
Because the indented EOF was not recognized, everything after it — including the real fi — was consumed as part of the here-document, leaving the if unterminated. Two clean fixes: put the terminator at column 0, or switch to <<- and use tabs:
if [ "$MODE" = canary ]; then
cat <<EOF
scaling canary...
EOF
kubectl scale ...
fi
bash -n then passed and line 84 stopped complaining.
Prevention Best Practices
- Syntax-check in CI — run
bash -n script.sh(and ShellCheck) on every change; it catches this before deploy. - Keep here-document terminators at column 0 with no trailing whitespace, or use
<<-with real tabs. - Indent consistently and fold blocks in your editor so an unclosed
if/foris visually obvious. - Normalize line endings — add a
.gitattributeswith*.sh text eol=lfand rundos2unixon imported scripts. - Close as you open — type
fi/done/esacimmediately afterif/do/case, then fill the body. - Run ShellCheck, which pinpoints the opening keyword that was never closed, not just the EOF.
Quick Command Reference
bash -n ./script.sh # syntax check, no execution
shellcheck ./script.sh # points at the unclosed opener
file ./script.sh # detects CRLF line terminators
dos2unix ./script.sh # strip \r that hides terminators
grep -cE '^\s*if\b|^\s*fi\b' script.sh # compare open/close counts
sed -n 'l' script.sh | tail # reveal hidden \r ($ at line end)
Related Guides
- Bash Error Guide: ‘syntax error near unexpected token’ — the same parser complaining mid-file instead of at EOF.
- Bash & Python Error Guide: ‘syntax error near unexpected token’ — quoting and CRLF variants across both languages.
- Bash Error Guide: ‘bad substitution’ — another parse-time failure, from shell feature mismatches.
Conclusion
syntax error: unexpected end of file means a block, quote, or here-document opened and never closed, so Bash ran out of input still waiting for the terminator. Ignore the reported (last) line number; instead run bash -n and ShellCheck, count your if/fi and do/done pairs, verify here-document terminators sit at column 0, and rule out CRLF. Close every block as you open it and this error becomes a one-line CI check rather than a deploy-time surprise.
Fixed it? Get 500 Bash & Python Automation & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.