Resolving Common Bash Syntax Errors: A Practical Guide
Fix common Bash syntax errors with examples for quotes, brackets, variables, redirects, and command lookup problems.
Resolving Common Bash Syntax Errors: A Practical Guide
Bash syntax errors usually come from small mistakes: a missing quote, a bad bracket, a misplaced redirect, or a variable that expanded into something you did not expect. When your Bash script stops with syntax error near unexpected token, start by checking the line before the one Bash reports, then run the script through a syntax check.
Use this quick check before you run a changed script on a server:
bash -n script.sh
bash -n parses the file without executing commands. It will not catch every logic bug, but it catches many broken quotes, missing fi statements, and malformed loops.
Missing Quotes
Unclosed quotes are one of the fastest ways to confuse the parser.
name="deploy
printf 'Deploying %s\n' "$name"
Bash keeps reading subsequent lines because it is still looking for the closing ". Fix it by closing the quote and quoting variable expansions that may contain spaces:
name="deploy"
printf 'Deploying %s\n' "$name"
Use double quotes when you want variables to expand. Use single quotes when you want literal text:
printf 'HOME stays literal: $HOME\n'
printf "HOME expands: %s\n" "$HOME"
Bad if, for, and while Blocks
Every compound command needs its closing keyword. Missing one often reports the error near the end of the file.
if systemctl is-active --quiet nginx; then
echo "nginx is running"
# missing fi
Correct version:
if systemctl is-active --quiet nginx; then
echo "nginx is running"
fi
The same pattern applies to loops and case statements:
for host in web1 web2 web3; do
ssh "$host" uptime
done
case "$env" in
prod) echo "production" ;;
dev) echo "development" ;;
*) echo "unknown" ;;
esac
Bracket and Test Mistakes
The [ command is a real command, so it needs spaces around its arguments and before the closing bracket.
Broken:
if [$count -gt 5]; then
echo "too many"
fi
Fixed:
if [ "$count" -gt 5 ]; then
echo "too many"
fi
For Bash-specific scripts, [[ ... ]] is often safer for string tests because it handles empty variables more gracefully and supports pattern matching:
if [[ "$file" == *.log ]]; then
gzip "$file"
fi
Use numeric operators for numbers and string operators for text:
[[ "$status" == "ready" ]] # string comparison
[[ "$retries" -lt 3 ]] # numeric comparison
Variable Expansion Problems
A common mistake is adding spaces around = during assignment. Bash treats that as a command instead of a variable assignment.
Broken:
backup_dir = /var/backups
Fixed:
backup_dir=/var/backups
Use braces when the variable name touches other text:
service="nginx"
log="/var/log/${service}.log"
Without braces, Bash may read a longer variable name than you intended.
command not found Errors
command not found is not always a syntax error. It usually means Bash could not find an executable with that name.
Check for typos first:
systemctl status nginx
Then check whether the command exists and is in PATH:
command -v systemctl
printf '%s\n' "$PATH"
If the script runs under cron, systemd, or a CI job, PATH may be shorter than your interactive shell. Use absolute paths for critical commands or set PATH near the top of the script:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH
Redirection and Pipe Errors
Redirection order matters. This command writes stdout to app.log and then sends stderr to the same place:
./deploy.sh >app.log 2>&1
This one is different because stderr is copied to the old stdout before stdout is redirected:
./deploy.sh 2>&1 >app.log
For pipelines, remember that Bash normally returns the exit code of the last command. Use pipefail when a failed command in the middle should fail the whole pipeline:
set -o pipefail
kubectl get pods | grep CrashLoopBackOff
A Practical Debugging Flow
Start with a syntax check:
bash -n script.sh
Run with tracing when the syntax is valid but behavior is wrong:
bash -x script.sh
For safer production scripts, add strict mode deliberately and test the script after each change:
set -euo pipefail
set -e exits on many command failures, set -u treats unset variables as errors, and pipefail catches failures inside pipelines. These options are useful, but they can change script behavior, so enable them intentionally instead of dropping them into an old script without testing.
Takeaway
Most Bash syntax errors become simple once you check quotes, closing keywords, bracket spacing, variable assignments, and redirects in that order. Keep bash -n and bash -x in your normal workflow, and test scripts with the same shell and environment that will run them in production.