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 fi

Fix

#!/usr/bin/env bash
if true; then
  echo yes
fi

# Run a syntax check without executing:
bash -n script.sh

Explanation

`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.