Bash Error Guide: 'unary operator expected' — Fix Empty Test Operands
Fix Bash 'unary operator expected': quote test operands so an empty or unset variable can't collapse '[ $x = y ]' into a broken comparison.
- #bash
- #automation
- #troubleshooting
- #errors
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
Bash prints unary operator expected when a test / [ ... ] comparison lost one of its operands before [ ran. The classic trigger is an unquoted variable that expanded to nothing:
./check.sh: line 12: [: =: unary operator expected
test is an ordinary command, and [ runs word-splitting on its arguments before it evaluates them. If you write [ $x = "prod" ] and $x is empty, the shell hands [ the arguments = and prod — only two words instead of three. [ sees a binary operator (=) sitting where it expected a single value and a unary operator, so it aborts. The fix is almost always to quote the operand.
Symptoms
- A comparison that works for most inputs fails only when a variable happens to be empty or unset.
- The error names the operator (
=,!=,-eq) as the thing that appeared where an operand should be. - Conditionals that “sometimes” take the wrong branch, or scripts that break only in a fresh environment where a variable hasn’t been exported.
[[ ... ]]versions of the same test do not fail — a strong hint the problem is word-splitting inside old-style[ ].
Common Root Causes
- Unquoted empty/unset variable —
[ $x = y ]becomes[ = y ]when$xis empty. - Left operand is a command substitution that printed nothing —
[ $(get_state) = up ]with no output. - A value that itself starts with
-—[ $flag = on ]whereflag="-n"makes[try to parse-nas a unary operator. - Comparing an array element or positional parameter that isn’t set —
[ $1 = deploy ]with no arguments passed. - A trailing/leading part of the value stripped by word-splitting so the operator lands in the wrong argument slot.
Diagnostic Workflow
Reproduce with argument counting so you can see how many words [ actually receives:
x=""
set -- $x = prod
printf 'argc=%d args=[%s]\n' "$#" "$*" # argc=2 args=[= prod] -> operand missing
Trace the failing line and watch the expansion:
bash -x ./check.sh 2>&1 | grep -n 'unary\|\[ '
Confirm the suspect variable is empty at the point of comparison:
printf 'x=[%q]\n' "$x" # [] means empty; a value shows escaped
Example Root Cause Analysis
A health-check script decided whether to page:
STATE=$(curl -s "$URL/health" | jq -r .state)
if [ $STATE = "healthy" ]; then
echo "ok"
else
page_oncall
fi
On a flaky endpoint it died with:
healthcheck.sh: line 4: [: =: unary operator expected
bash -x showed the cause:
+ STATE=
+ '[' = healthy ']'
healthcheck.sh: line 4: [: =: unary operator expected
curl had failed, jq printed nothing, and $STATE was empty. Unquoted, the test collapsed to [ = healthy ]. Worse, the error aborted the if in a way that skipped paging entirely. The robust fix quotes both operands and defaults the value:
STATE=$(curl -s "$URL/health" | jq -r .state)
STATE=${STATE:-unknown}
if [ "$STATE" = "healthy" ]; then
echo "ok"
else
page_oncall
fi
Quoting "$STATE" guarantees a single argument even when empty, so [ "" = healthy ] evaluates cleanly to false instead of erroring.
Prevention Best Practices
- Always quote both operands —
[ "$x" = "y" ], never[ $x = y ]. This one habit removes almost every occurrence. - Prefer
[[ ... ]]in Bash — it doesn’t word-split unquoted variables, so an empty operand stays a single empty string. - Default risky values —
${x:-}or${x:-unknown}so a comparison always has something to compare. - Enable
set -uto catch truly unset variables by name before they reach a test. - Guard command-substitution results — capture into a variable, check it’s non-empty, then compare.
- Run ShellCheck — it flags unquoted test operands (SC2086) and suggests
[[ ]].
Quick Command Reference
[ "$x" = "prod" ] # quoted: safe even when $x is empty
[[ $x == prod ]] # [[ ]] never word-splits the operand
x=${x:-unknown} # default so the operand is never missing
set -- $x = y; echo "$#" # count words handed to [ (debugging)
bash -x ./script.sh # trace the real expansion
shellcheck ./script.sh # SC2086 flags the unquoted operand
Related Guides
- Bash Error Guide: ‘integer expression expected’ — the numeric cousin, when
-eq/-ltget a non-number. - Bash Error Guide: ‘unbound variable’ — catch the empty/unset variable before it reaches the test.
- Bash & Python Error Guide: ‘syntax error near unexpected token’ — other word-splitting and quoting failures in tests.
Conclusion
unary operator expected means [ lost an operand to word-splitting — usually an unquoted variable that expanded to nothing. Quote both sides of every comparison, prefer [[ ... ]] in Bash, and default risky values with ${x:-}. Do that and the test evaluates to a clean true/false even in the edge case where the value is empty, instead of aborting your conditional at the worst possible moment.
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?
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.