Decoding "syntax error near unexpected token"
What You'll Learn
- How to read the
syntax error near unexpected tokenmessage - Why the token's position and the real cause often differ
- How to debug by token:
fi/then/done/(/newline/end of file
Quick Summary
In syntax error near unexpected token 'X', the X is where bash gave up parsing, and the actual mistake is usually before it.
- Classify by the token (
fi/then/done= control flow,(= parentheses / special chars,newline/end of file= something unclosed) - Read the error line and the line just before it (suspect what comes before the token, not the token itself)
- Check syntax only with
bash -n script.sh(find the line without running it) - Inspect line endings and hidden chars with
cat -A
Assumptions
- Shell: bash (the default on Ubuntu / Debian)
- Target: a
.shscript you wrote, or a command typed in an interactive shell /bin/sh(dash) prints different wording (covered below)
What is syntax error near unexpected token?
Conclusion: While bash parses your command, it hit a word (token) that cannot legally appear there. It stops at the syntax-check stage, before running anything.
Before running a command, bash first parses it to check that the words are arranged according to its grammar. The moment it decides "this word cannot legally be here," it prints this error and runs nothing.
$ echo hello world) bash: syntax error near unexpected token `)'
Here ) is the unexpected token. bash read echo hello world fine, but a ) appeared with no matching (, so it flagged a syntax error.
While command not found means "the command was not located (but the grammar was fine)," syntax error means "this is not even a valid statement." Nothing was executed.
Why does the token position differ from the real cause?
Conclusion: bash reads left to right and stops at the first point it can no longer interpret correctly. The reported token is that stopping point; the cause (something unclosed or missing) is usually before it.
This is the biggest trap with this error. Consider:
if [ "$x" -gt 0 ] echo "big" fi
$ bash script.sh script.sh: line 3: syntax error near unexpected token `fi'
The report points at fi on line 3, but the real cause is the missing then on line 1. bash was waiting for then after if [ ... ], got echo instead, then fi, and only then decided it could no longer interpret the input. So it reports the point where it stopped (fi).
Reading tip
When the token is a closing/terminating word () / } / fi / done / esac / end of file), suspect a problem with the matching opening side. Don't fix the token's position; review the structure before it.
When the token is fi / then / done
Conclusion: A missing separator (
;or newline), a missingthen/do, or an unclosed block. Check thatif-fiandfor-donepair up.
When the structure of if / for / while / case is broken, these keywords become the unexpected token.
No separator before then
A ; or newline is required between the condition and then.
# NG: no separator between ] and then if [ "$x" = "1" ] then echo ok fi
$ bash script.sh script.sh: line 3: syntax error near unexpected token `fi'
# OK: separate with ; (or put then on the next line) if [ "$x" = "1" ]; then echo ok fi
Missing do, or an unmatched done / fi
# NG: the do is missing for f in *.txt echo "$f" done
$ bash script.sh script.sh: line 3: syntax error near unexpected token `done'
for ... in ... must be followed by do. Reaching done with no do triggers this error.
When you paste only some lines, it's easy to drop a lone if or do and leave the structure half-open. Always verify a block's opening and closing as a pair.
When the token is ( or a symbol
Conclusion: An unquoted shell special character such as
()&;|<>. Wrap symbols inside filenames or strings in quotes.
( and ) are special characters used for subshells and function definitions, so writing them bare in a filename or argument causes a syntax error.
# NG: the () in the filename is read as special characters
$ cp report(final).txt /backup/
bash: syntax error near unexpected token `('Fix it by quoting or escaping.
# OK: wrap in quotes $ cp 'report(final).txt' /backup/ # OK: escape with a backslash $ cp report\(final\).txt /backup/
The same happens with & (background) or ; (command separator).
# NG: & is read as a background operator $ echo Tom & Jerry
Always quote any symbol you mean to pass as literal text.
A paste-time trap
Copying from the web or docs can turn " into curly "smart quotes" (" "). They look similar but bash treats them as different characters, so the quote never closes and you get a syntax error. Check with cat -A, or retype the quotes by hand.
When the token is newline or end of file
Conclusion: An unclosed quote, parenthesis, here-document, or control block. bash read to the end of the file without finding the close and stopped at the terminator.
unexpected token 'newline' or unexpected end of file is a sign that something opened was never closed.
An unclosed quote
$ echo "hello >
The opened " is never closed, so bash treats the next line as a continuation of the string and keeps waiting for input (the > prompt). In a script, it stops at the end.
echo "hello echo "world"
$ bash script.sh script.sh: line 3: unexpected EOF while looking for matching `"' script.sh: line 4: syntax error: unexpected end of file
A mismatched here-document terminator
# NG: the terminator does not match (not at the start of the line / has leading spaces) cat <<EOF hello EOF
A here-document terminator must be an exact label starting at the beginning of the line. Indent it and it is not recognized, so you get unexpected end of file at the end of the file (<<-EOF only allows leading tabs).
Are you running it with sh?
Conclusion: Running bash-only syntax (
[[ ]], arrays, process substitution) withsh script.shcauses a syntax error. Run it with bash, or set the shebang to#!/bin/bash.
On Ubuntu, /bin/sh is dash, which does not understand bash extensions. A script that works under bash will fail when run with sh.
# A script with bash-only arrays and [[ ]] arr=(a b c) [[ -n "$1" ]] && echo "$1"
# NG: run with sh (dash)
$ sh script.sh
script.sh: 1: Syntax error: "(" unexpecteddash prints Syntax error: "(" unexpected with different wording, but the cause is the same: syntax that POSIX sh does not support. Fix it one of these ways.
# 1. Run it explicitly with bash $ bash script.sh # 2. Set the shebang to bash and run it directly $ head -1 script.sh #!/bin/bash $ ./script.sh
./script.sh runs under the interpreter in the shebang, but sh script.sh ignores the shebang and runs under dash. Check that you aren't accidentally prefixing sh. See Fixing "bad interpreter" for details.
When CRLF line endings are the cause
Conclusion: Scripts edited on Windows carry a
\r(CR) at each line end, causing syntax errors and odd behavior. Spot^Mwithcat -Aand convert withdos2unix.
A script saved by a Windows editor uses CRLF (\r\n) line endings, leaving an invisible \r at the end of every line. It sticks to keywords and quotes and triggers syntax errors or command not found.
# the ^M at line ends shows CR contamination $ cat -A script.sh #!/bin/bash^M$ if [ "$x" = "1" ]; then^M$ echo ok^M$ fi^M$
$ marks the line end and ^M is the CR. Convert it to LF.
# shortest, if dos2unix is available $ dos2unix script.sh # otherwise strip it with sed / tr $ sed -i 's/\r$//' script.sh
Set your editor to save with "line endings: LF" and "encoding: UTF-8" to prevent recurrence. Adding *.sh text eol=lf to .gitattributes also works well.