Bash error: missing `fi` (unexpected end of file)
When Bash reaches the end of a script while still expecting `fi`, `done`, `esac`, or a closing brace, it reports "unexpected end of file". Match every opener.
Error String
bash: syntax error: unexpected end of file
Tldr
Bash hit EOF while parsing an unfinished block. Track down the unmatched opener: every `if` needs `fi`, every `for`/`while`/`until` needs `done`, every `case` needs `esac`, every `{` needs `}`, and every heredoc needs its delimiter.
Cause
A typo such as `fi` written as `if`, an extra opening `{`, or a heredoc whose terminator has trailing whitespace will all leave the parser in an unfinished state.
Repro
#!/usr/bin/env bash
if true; then
echo yes
# missing fiFix
#!/usr/bin/env bash
if true; then
echo yes
fi
# Run a syntax check without executing:
bash -n script.shExplanation
`bash -n` parses the script and reports the line where the parser gave up — usually close to where the missing terminator should be. ShellCheck and bashchecker also catch this immediately.
Faq
Q
Does elif need its own fi?
A
No. `if`/`elif`/`else` form one construct closed by a single `fi`.
Q
Why is the reported line the end of the file?
A
The parser only knows the keyword is missing when input runs out, so the end of file is where it gives up.
Deep Dive
Heading
Every if needs its own fi
Body
Bash reads to end of file looking for the closing keyword, so an omitted `fi` is reported at the last line rather than at the branch that is open. Nested conditionals inside loops and functions are the usual cause, especially after deleting an `elif` block and leaving its `fi` behind — or removing the wrong one. `case` needs `esac`, `for`/`while` need `done`, and a function body needs its closing brace on its own line or preceded by a semicolon.
Heading
Finding it quickly
Body
Run `bash -n script.sh`, which parses without executing. Then re-indent the file — most editors have a shell formatter such as `shfmt -w` — because a misplaced keyword shows up immediately once indentation is derived from structure rather than typed by hand. ShellCheck reports the construct that was left open and usually names the line where it started, which is far more useful than the parser message.
Checklist
Run `bash -n script.sh` after every edit to a conditional.
Reformat with `shfmt` so nesting is visible.
Check functions and loops for a missing `}` or `done` as well.
Look at the most recently edited block, not the reported line.