「syntax error near unexpected token」の読み解き方

「syntax error near unexpected token」の読み解き方

この記事で解決できること

  • syntax error near unexpected tokenメッセージの読み方 が分かる
  • token が指す位置と 本当の原因がずれる理由 を理解できる
  • fi / then / done / ( / newline / end of file など token 別に原因を切り分け できる

結論(読み解きの型)

syntax error near unexpected token 'X'X は「bash がそこで構文として行き詰まった地点」であり、ミスは多くの場合その手前にある。

  1. token 名を見て分類するfi/then/done なら制御構文、( なら括弧・特殊文字、newline/end of file なら閉じ忘れ)
  2. エラー行とその直前を読む(token の位置ではなく手前を疑う)
  3. bash -n script.sh で構文だけ検査(実行せず行番号を特定)
  4. 改行コード・不可視文字を cat -A で確認

前提(対象環境)

  • シェル: bash(Ubuntu / Debian 系の既定)
  • 対象: 自分で書いた .sh スクリプト、または対話シェルで打ったコマンド
  • /bin/sh(dash)はメッセージの文言が異なる(後述)

syntax error near unexpected token とは何か?

結論: bash がコマンドを解析(パース)する段階で、文法上そこに来てはいけない単語(token)に出くわした、というエラー。実行前の「構文チェック」で止まっている状態。

bash はコマンドを実行する前に、まず文法どおりに並んでいるかを解析する。その途中で「ここにこの単語が来るのは文法的におかしい」と判断すると、実行せずにこのエラーを出す。

$ echo hello world)
bash: syntax error near unexpected token `)'

ここで )unexpected token(予期しない単語)として報告されている。echo hello world までは正しく読めたが、対応する ( が無いまま ) が現れたため、bash は文法エラーと判断した。

command not found が「コマンドは見つからない(が文法は正しい)」エラーなのに対し、syntax error は「そもそも文として成立していない」エラーである。実行は一切行われていない。

なぜ token の位置と本当の原因がずれるのか?

結論: bash は左から順に読み進め、「これ以上正しく解釈できない」最初の地点で停止する。報告される token はその停止地点であり、原因(閉じ忘れ・付け忘れ)はその手前にあることが多い。

これがこのエラー最大のハマりどころである。例を見る。

if [ "$x" -gt 0 ]
  echo "big"
fi
$ bash script.sh
script.sh: line 3: syntax error near unexpected token `fi'

報告されたのは 3 行目の fi だが、本当の原因は 1 行目の then 忘れである。bash は if [ ... ] の後に then が来るはずだと待っているのに、echo が来て、さらに fi が来て、ようやく「もう正しく解釈できない」と判断した。だから止まった地点(fi)が報告される。

読み方のコツ

token が「閉じ・終端」を表す単語() / } / fi / done / esac / end of file)のときは、対応する開き側に問題があると疑う。token の位置を直すのではなく、手前の構造を見直す。

token が fi / then / done のときは?

結論: 制御構文の区切り(; や改行)の付け忘れ、then / do の欠落、ブロックの閉じ忘れが原因。iffifordone の対応を確認する。

if / for / while / case の構造が崩れていると、これらの予約語が unexpected token になる。

then の前に区切りが無い

if の条件と then の間には ; か改行が必要。

# NG: ] と then の間に区切りが無い
if [ "$x" = "1" ] then
  echo ok
fi
$ bash script.sh
script.sh: line 3: syntax error near unexpected token `fi'
# OK: ; で区切る(または then を次の行へ)
if [ "$x" = "1" ]; then
  echo ok
fi

done / fi の閉じ忘れ・余り

# NG: do を付け忘れている
for f in *.txt
  echo "$f"
done
$ bash script.sh
script.sh: line 3: syntax error near unexpected token `done'

for ... in ... の後には do が必要。do が無いまま done に到達するとこのエラーになる。

コピペで一部の行だけ貼り付けたとき、if だけ・do だけが欠けて構造が片肺になりやすい。ブロックは開きと閉じをセットで確認する。

token が ( や記号のときは?

結論: クォートしていない ( ) & ; | < > などのシェル特殊文字が原因。ファイル名や文字列に含まれる記号は引用符で囲む。

( ) はサブシェルや関数定義に使う特殊文字なので、ファイル名や引数に裸で書くと文法エラーになる。

# NG: ファイル名の () が特殊文字として解釈される
$ cp report(final).txt /backup/
bash: syntax error near unexpected token `('

対処はクォートまたはエスケープ。

# OK: クォートで囲む
$ cp 'report(final).txt' /backup/

# OK: バックスラッシュでエスケープ
$ cp report\(final\).txt /backup/

同じことが &(バックグラウンド実行)や ;(コマンド区切り)でも起きる。

# NG: & がバックグラウンド指定として解釈される
$ echo Tom & Jerry

文字列として渡したい記号は必ず引用符で囲む。

貼り付け時の落とし穴

Web やドキュメントからコピーすると、" が全角の " "(スマートクォート)に化けていることがある。見た目は似ていても bash は別物として扱い、引用が閉じられず syntax error になる。cat -A で確認するか、手で打ち直す。

token が newlineend of file のときは?

結論: クォート・括弧・ヒアドキュメント・制御ブロックの閉じ忘れが原因。bash がファイル末尾まで読んでも「閉じ」が見つからず終端で停止している。

unexpected token 'newline'unexpected end of file は「開いたものが閉じられていない」サイン。

クォートの閉じ忘れ

$ echo "hello
>

開いた " が閉じられていないため、bash は次の行を文字列の続きと見なして入力を待ち続ける(> プロンプト)。スクリプトなら末尾で停止する。

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

ヒアドキュメントの終端ラベル不一致

# NG: 終端ラベルが本文 EOF と一致していない(行頭でない / スペース混入)
cat <<EOF
hello
  EOF

ヒアドキュメントの終端は行頭から始まる完全一致のラベルが必要。インデントすると終端と認識されず、ファイル末尾で unexpected end of file になる(<<-EOF ならタブのみ許容)。

sh で実行していないか?

結論: bash 専用構文([[ ]]・配列・プロセス置換)を sh script.sh で実行すると構文エラーになる。bash で実行するか shebang を #!/bin/bash にする。

Ubuntu の /bin/sh は dash であり、bash の拡張構文を解釈できない。bash なら通るスクリプトでも sh で動かすと止まる。

# bash 専用の配列・[[ ]] を含むスクリプト
arr=(a b c)
[[ -n "$1" ]] && echo "$1"
# NG: sh(dash)で実行
$ sh script.sh
script.sh: 1: Syntax error: "(" unexpected

dash のメッセージは Syntax error: "(" unexpected で文言が異なるが、原因は同じ「POSIX sh では未対応の構文」。対処は次のいずれか。

# 1. bash で明示的に実行する
$ bash script.sh

# 2. shebang を bash にして直接実行する
$ head -1 script.sh
#!/bin/bash
$ ./script.sh

./script.sh は shebang のインタプリタで動くが、sh script.sh は shebang を無視して dash で動く。意図せず sh を付けて実行していないか確認する。詳しくは「bad interpreter」エラーの直し方を参照。

CRLF 改行が原因のときは?

結論: Windows で編集したスクリプトは行末に \r(CR)が混入し、構文エラーや謎の挙動を起こす。cat -A^M を確認し dos2unix で変換する。

Windows のエディタで保存したスクリプトは改行が CRLF(\r\n)になり、各行末に見えない \r が付く。これが予約語や引用符にくっつき、構文エラーや command not found を誘発する。

# 行末の ^M が CR の混入を示す
$ cat -A script.sh
#!/bin/bash^M$
if [ "$x" = "1" ]; then^M$
echo ok^M$
fi^M$

$ が行末、^M が CR。これを LF に変換する。

# dos2unix があれば最短
$ dos2unix script.sh

# 無ければ sed / tr で除去
$ sed -i 's/\r$//' script.sh

エディタ側で「改行コード: LF」「文字コード: UTF-8」に設定して保存すれば再発を防げる。.gitattributes*.sh text eol=lf を書く運用も有効。

切り分けチェックリスト

結論: 「token 名で分類 → 手前を疑う → bash -n で構文検査 → cat -A で不可視文字確認」の順にたどれば、syntax error near unexpected token はほぼ特定できる。

上から順に確認する。

  • [ ] token 名を確認した(fi/then/done → 制御構文、( → 特殊文字、newline/end of file → 閉じ忘れ)
  • [ ] token の位置ではなくその手前の行を読み直した
  • [ ] bash -n script.sh で構文だけ検査し、報告行を特定した
  • [ ] iffi / fordone の開き・閉じが対応しているか確認した
  • [ ] クォート・括弧・ヒアドキュメントが閉じているか確認した
  • [ ] ファイル名や引数の特殊文字を引用符で囲んだ
  • [ ] sh script.sh ではなく bash script.sh / ./script.sh で実行した
  • [ ] cat -A^M(CR)が無いか確認し、あれば dos2unix

次に読む記事: