set -u, nounset, and Safer Variable Access
`set -u` catches misspelled or forgotten variables, but optional configuration and positional arguments still need deliberate handling. Safe strict-mode code distinguishes unset, empty, optional, and required values.
Learning objectives
By the end of this lesson
- Explain unset versus empty values.
- Use default and required parameter expansion.
- Read positional arguments safely.
- Check missing array/map elements.
- Normalize optional configuration early.
1. nounset turns unset expansion into an error
set -u
printf '%s\n' "$NOT_DEFINED"set -u (or set -o nounset) helps catch misspelled variable names and forgotten initialization by treating many unset expansions as errors.
2. Unset and empty are different states
set -u
empty=""
printf 'empty=<%s>\n' "$empty"
# unset_value is not defined:
# printf '%s\n' "$unset_value"An explicitly empty string is still set. This distinction is important in configuration precedence and optional values.
3. Parameter expansion provides safe defaults
set -u
environment=${DEPLOY_ENV:-staging}
printf 'environment=%s\n' "$environment"${var:-default} uses the default when the variable is unset or empty. The no-colon form ${var-default} only substitutes when the variable is unset.
4. Required-variable expansion can fail with a message
set -u
: "${API_TOKEN:?API_TOKEN must be set}"
: "${DEPLOY_ENV:=staging}"
printf 'env=%s\n' "$DEPLOY_ENV":? is useful for required configuration and := can assign a default. Keep messages actionable and avoid echoing secrets.
5. Missing positional parameters need guarded access
set -u
service=${1:-}
if [[ -z $service ]]; then
printf 'usage: %s SERVICE\n' "$0" >&2
exit 64
fiDirectly expanding $1 under nounset when no argument exists can terminate the script before your usage message runs.
6. Arrays and missing elements require deliberate access
set -u
items=("api" "worker")
printf 'first=%s\n' "${items[0]}"
if [[ -v 'items[5]' ]]; then
printf 'fifth=%s\n' "${items[5]}"
fiUse [[ -v expression ]] when you need to know whether a specific variable or array element is set.
7. Associative-array missing keys are another nounset boundary
set -u
declare -A config=([environment]=staging)
if [[ -v 'config[region]' ]]; then
region=${config[region]}
else
region=eu-central-1
fi
printf 'region=%s\n' "$region"8. Indirect and dynamically named variables deserve extra care
set -u
name='DEPLOY_ENV'
value=${!name-}
printf 'value=<%s>\n' "$value"Dynamic variable access can make nounset failures harder to read. Prefer ordinary variables or associative arrays when possible.
9. Optional environment variables should be normalized early
set -u
verbose=${VERBOSE:-false}
region=${REGION:-eu-central-1}
timeout_seconds=${TIMEOUT_SECONDS:-30}Normalize optional inputs near startup so later code can work with defined variables and clearer invariants.
10. Local variables should be initialized before use
set -u
deploy() {
local service=${1:-}
local environment=${2:-staging}
[[ -n $service ]] || {
printf 'service required\n' >&2
return 64
}
printf 'service=%s env=%s\n' "$service" "$environment"
}11. Hands-on lab: nounset-safe configuration
mkdir -p "$HOME/devops-academy/bash/chapter11/lesson02"
cd "$HOME/devops-academy/bash/chapter11/lesson02"
cat > config-demo.sh <<'EOF'
#!/usr/bin/env bash
set -u
service=${1:-}
environment=${DEPLOY_ENV:-staging}
replicas=${REPLICAS:-2}
if [[ -z $service ]]; then
printf 'usage: %s SERVICE\n' "$0" >&2
exit 64
fi
[[ $replicas =~ ^[0-9]+$ ]] || {
printf 'REPLICAS must be numeric\n' >&2
exit 65
}
printf 'service=%s env=%s replicas=%s\n' \
"$service" "$environment" "$replicas"
EOF
bash config-demo.sh api
DEPLOY_ENV=prod REPLICAS=3 bash config-demo.sh worker
bash config-demo.sh || trueVerification checklist
12. Knowledge check
Question 1. What does nounset catch?
Question 2. What is the difference between ${v:-x} and ${v-x}?
Question 3. How should missing positional parameters be read under nounset?
${1:-} before validation.Question 4. Why normalize optional environment variables early?
13. Summary
set -u catches an important class of shell bugs, but optional values must be handled intentionally. Guard positional parameters, distinguish unset from empty, initialize locals, and normalize configuration near the start of the script.
14. Further reading
- GNU Bash Reference Manual —
nounset. - GNU Bash Reference Manual — Shell Parameter Expansion.
- GNU Bash Reference Manual — Conditional Expressions and
-v. - ShellCheck documentation — unset-variable patterns.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this address.