Bash rejects the script at parse time. Half the cases are quoting/paren mistakes; the sneaky other half are CRLF line endings from Windows editors that make valid code unparseable on Linux.
Windows-saved scripts carry \r. Bash sees 'then\r' as a different token. cat -A file | grep -c '\^M\$' detects it; dos2unix or sed -i 's/\r$//' fixes it.
Unquoted ( ) | < > in test contexts, a missing then/fi/done, or a function defined where bash expects a command. bash -n file parses without running — fastest loop for finding them.
cat -A script.sh | head -5 # '^M$' at line ends = CRLF; then: sed -i 's/\r$//' script.sh
bash -n script.sh # pinpoints the first parse error line
# wrap in quotes: grep "foo(bar)" — unquoted parens are subshell syntax to bash
git config core.autocrlf input # and .gitattributes: *.sh text eol=lf
ShellCheck finds not just this error but ten future ones: install it, run sh check.sh script.sh. Shebang mismatch (sh vs bash) changes the grammar: sh on Debian is dash, stricter than bash.
Line endings: your Windows/macOS editor saved CRLF, and CI's Linux shell chokes. Fix with dos2unix or the .gitattributes rule, then verify with cat -A.
Often the error is a CR at the end of a control keyword ('then\r') — bash reports the token, not the invisible character. cat -A exposes the ^M immediately.
Our most-documented failures, packaged as ready-to-ship starter kits: Docker, Kubernetes, and Terraform.
Browse the template store →One-time. Yours to modify. Instant download from the NinjaOps template store.