Bash error: "unbound variable"
Fix "bash: unbound variable" under set -u. Use ${VAR:-default}, ${VAR:=default}, or guard $# before reading $1 — with working examples for each case.
Error String
bash: VAR: unbound variable
Tldr
You get "bash: NAME: unbound variable" when set -u (nounset) is enabled and the script reads a variable that was never assigned. Fix it by giving the variable a default with ${NAME:-default} or by assigning it before use.
Cause
Bash by default treats unset variables as empty strings. When you turn on set -u (also written set -o nounset), any expansion of a variable that has never been assigned aborts the script with this error. The most common triggers are typos in variable names, environment variables you assumed were exported, and positional parameters ($1, $2…) used before checking $#.
Repro
#!/usr/bin/env bash
set -euo pipefail
echo "Hello, $NAME" # NAME was never set -> aborts hereFix
#!/usr/bin/env bash
set -euo pipefail
# Option 1: provide a default in the expansion
echo "Hello, ${NAME:-world}"
# Option 2: assign before use
: "${NAME:=world}"
echo "Hello, $NAME"
# Option 3: guard positional parameters
if [ "$#" -lt 1 ]; then
echo "Usage: $0 NAME" >&2
exit 64
fi
echo "Hello, $1"Explanation
The "${VAR:-default}" form expands to "default" when VAR is unset OR empty. Use "${VAR-default}" if you want the default only when VAR is unset but still allow an explicit empty value. The ":=" form additionally assigns the default back to the variable, which is handy when you reference it multiple times.
Related Shellcheck
SC2154
Deep Dive
Heading
Which expansion form should you use
Body
`${VAR:-default}` substitutes when VAR is unset or empty and leaves VAR untouched. `${VAR-default}` substitutes only when VAR is unset, so an intentional empty value survives. `${VAR:=default}` substitutes and assigns, which is right when the variable is read in several places. `${VAR:?message}` aborts with your own message instead of the generic Bash one — useful for required inputs where failing fast is the correct behaviour. For arrays, `"${arr[@]:-}"` expands to nothing instead of aborting when the array was never assigned.
Heading
Why it usually shows up in CI and not locally
Body
Most of these failures are environment variables that exist in your shell profile but never get exported into the CI runner or the container. The script works on your laptop and dies in the pipeline. Treat every environment variable the script reads as an explicit input: validate it at the top of the script with `: "${DEPLOY_ENV:?DEPLOY_ENV is required}"` so the failure names the missing variable instead of failing halfway through a deploy.
Heading
Positional parameters and set -u
Body
With `set -u`, referencing `$1` when no argument was passed aborts. Check `$#` first, or expand defensively with `"${1:-}"`. The same applies inside functions — a function that reads `$2` must be called with two arguments or use a default. A usage block that prints to stderr and exits with 64 (EX_USAGE) is the conventional fix.
Checklist
Read the error text: the name before "unbound variable" is the exact variable Bash could not resolve.
Grep the script for that name — a typo or case mismatch is the most common cause.
If the name is correct, check whether it is meant to come from the environment and whether it is exported where the script runs.
Decide the intended behaviour: a safe default (${VAR:-...}), an assignment (${VAR:=...}), or a hard failure with a message (${VAR:?...}).
For positional parameters, guard on "$#" before reading $1, $2, and so on.
Faq
Q
Why does it work without set -u?
A
Without set -u Bash silently treats unset variables as empty strings. set -u is a safety net that surfaces typos and missing inputs early — keep it on and fix the call sites.
Q
How do I check if a variable is set without aborting?
A
Use [ -v VAR ] in Bash 4.2+ or test "${VAR+x}" — both work even with set -u because :+ and +x do not trigger the nounset check.
Q
Does this affect arrays?
A
Yes. Reading "${arr[0]}" before the array is assigned triggers the same error. Use "${arr[@]:-}" to expand to nothing safely.