Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Bash & Python Automation By James Joyner IV · · 9 min read Last reviewed Jul 2026

Bash Error Guide: 'syntax error near unexpected token' — Fix Quoting, CRLF & Heredocs

Quick answer

Fix Bash 'syntax error near unexpected token' errors: track down bad quoting, Windows CRLF line endings, unclosed heredocs, function-name typos, and running a script under the wrong shell.

  • #bash
  • #automation
  • #troubleshooting
  • #errors
Free toolkit

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

A script that looks fine to the eye refuses to run, and Bash aborts at parse time — before a single command executes — pointing at a token it did not expect:

./deploy.sh: line 24: syntax error near unexpected token `('

Other common variants name a different token but mean the same class of problem:

./deploy.sh: line 42: syntax error near unexpected token `done'
./deploy.sh: line 5: syntax error near unexpected token `fi'
./deploy.sh: line 88: syntax error near unexpected token `$'\r''

This is a parse error, not a runtime error. Bash reads the whole compound structure (a loop, an if, a function, a subshell) and finds that a token arrived where the grammar didn’t allow it — usually because an earlier construct was never closed, a keyword is missing, invisible characters snuck in, or the file isn’t even being run by Bash. The line number is where Bash gave up, which is frequently downstream of the actual mistake.

Symptoms

  • The script fails instantly with a non-zero status and runs none of its commands — parsing failed before execution.
  • The reported line contains a done, fi, then, ), }, or ( that is itself perfectly valid — the real error is above it.
  • A $'\r' in the token name (e.g. near unexpected token `$'\r') — a dead giveaway of Windows CRLF line endings.
  • The same file runs with bash script.sh but fails with sh script.sh or ./script.sh.
  • A function or case statement triggers it around ( / ) on the first line of a branch.
  • Pasting the code into a fresh terminal works, but the saved file fails — pointing at editor/encoding artifacts.

Common Root Causes

  • Windows (CRLF) line endings — a trailing \r on every line turns then\r, do\r, fi\r into tokens Bash can’t match; extremely common on files edited on Windows or checked out with autocrlf=true.
  • Wrong interpreter — the script uses Bash-only syntax ([[ ]], <(), arrays, function) but is run with sh (dash) via sh script.sh or a #!/bin/sh shebang.
  • Unclosed construct above the reported line — a missing fi, done, esac, closing ), or } makes Bash keep parsing until it hits the next keyword and chokes there.
  • Missing ; or newline before a keywordif [ -f x ] then (no ;/newline before then), or { cmd } without the required ; before }.
  • Function-definition typosfunction foo { fine, but foo () { with a stray character, or a redirection like >(...) process substitution run under sh.
  • An unbalanced quote or backtick earlier — an unterminated ", ', or ` swallows lines until a later token lands in the wrong context.
  • A stray or smart character — a “curly” quote pasted from a doc, a non-breaking space, or a leftover merge-conflict marker (<<<<<<<).

Diagnostic Workflow

Bash can parse-check a script without running it using -n (noexec). This is the single most useful command here:

bash -n ./deploy.sh
./deploy.sh: line 24: syntax error near unexpected token `('

Check for CRLF line endings, which are invisible in most editors. file and a hex peek both reveal them:

file ./deploy.sh
# deploy.sh: Bash script, ASCII text executable, with CRLF line terminators   <-- smoking gun

cat -A ./deploy.sh | sed -n '20,26p'
# every line ending in ^M$ confirms CRLF

Confirm which interpreter is actually running the script — a #!/bin/sh shebang on many distros is dash, not Bash:

head -1 ./deploy.sh
grep -nE '\[\[|<\(|=\(|\bfunction\b|declare -A' ./deploy.sh   # Bash-only features

Find the unbalanced construct by counting openers vs closers around the reported line:

grep -nE '\b(if|then|fi|for|while|do|done|case|esac)\b' ./deploy.sh | sed -n '1,40p'

Trace with ShellCheck, which pinpoints the real source line far better than Bash’s own message:

shellcheck ./deploy.sh

Example Root Cause Analysis

A deploy script fails in CI but runs on the author’s Mac:

$ ./deploy.sh
./deploy.sh: line 12: syntax error near unexpected token `$'{\r''
./deploy.sh: line 12: `deploy_service() {'

Line 12 looks completely normal:

deploy_service() {

The token in the error — $'{\r' — is the tell. That \r means the file has CRLF line endings: Bash sees deploy_service() {␍ and the carriage return breaks the function-definition grammar. The file was edited on Windows and committed with git config core.autocrlf true, so every line gained a trailing \r. It “worked on the Mac” only because that checkout happened to have LF endings.

The fix is to strip the carriage returns, not to touch the (correct) Bash syntax:

sed -i 's/\r$//' ./deploy.sh          # portable in-place strip
# or:
dos2unix ./deploy.sh

Then prevent recurrence: add a .gitattributes pinning shell scripts to LF, and re-run bash -n to confirm a clean parse:

*.sh text eol=lf
bash -n ./deploy.sh && echo "parses clean"

Prevention Best Practices

  • Parse-check in CI with bash -n script.sh on every shell file — it catches these errors before deploy without executing anything.
  • Run ShellCheck in pre-commit and CI; it flags the true offending line, unbalanced constructs, and sh-vs-Bash feature misuse (SC1009, SC1073, SC3010).
  • Pin line endings with a .gitattributes (*.sh text eol=lf) so no one’s editor or autocrlf setting reintroduces CRLF.
  • Always invoke with the right interpreter — use a #!/usr/bin/env bash shebang and run ./script.sh, never sh script.sh, when using Bash features.
  • Add ; or newlines before keywordsif ...; then, while ...; do, { cmd; }; make it a habit so keyword-adjacency errors never occur.
  • Configure your editor to show whitespace and reject smart quotes / non-breaking spaces in code files.

Quick Command Reference

# Parse-check without executing (best first move)
bash -n ./script.sh

# Detect CRLF / encoding issues
file ./script.sh
cat -A ./script.sh | sed -n '1,40p'      # ^M$ = CRLF

# Strip carriage returns in place
sed -i 's/\r$//' ./script.sh             # or: dos2unix ./script.sh

# Lint for the real offending line
shellcheck ./script.sh

# Confirm the interpreter / Bash-only features
head -1 ./script.sh
grep -nE '\[\[|<\(|declare -A' ./script.sh

# Pin LF endings for the repo
printf '*.sh text eol=lf\n' >> .gitattributes

Conclusion

syntax error near unexpected token is Bash failing to parse your script, so nothing ran and nothing was harmed — but the reported line is where Bash stumbled, not always where you erred. Start with bash -n to confirm it’s a parse error, then rule out the usual suspects: CRLF line endings (the $'\r' tell), the wrong interpreter (sh vs bash), and an unclosed if/do/quote above the flagged line. Let ShellCheck point at the true source, pin your line endings with .gitattributes, and add bash -n to CI so these parse failures are caught long before a deploy.

Free download · 368-page PDF

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?

Free download · 368-page PDF

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.