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 here

Fix

#!/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.