macOS Bash: declare -A Fails with "unbound variable"¶
Context¶
macOS ships with Bash 3.2 (frozen since 2007 due to GPLv3 licensing). Bash 3.2 does not support associative arrays (declare -A). When a script using declare -A is run under the system bash with set -u (nounset) active, Bash 3.2 misparses the array key syntax and aborts immediately.
Error observed:
Prerequisites¶
- macOS with Homebrew installed
- Terminal access
Root Cause¶
macOS /bin/bash is 3.2. Associative arrays (declare -A) require Bash 4.0+. With set -u active, the failed parse of ["container-runtime"] causes container to be treated as an unset variable — triggering an immediate abort.
Why is macOS Bash stuck at 3.2?
Apple has not updated the system bash since 2007 because Bash switched to the GPLv3 license with version 4.0. Apple only ships GPLv2-licensed software in the base OS.
Fix¶
Option 1 — Install Bash 5.x via Homebrew (preferred)¶
Update the script shebang to pin it to Homebrew bash explicitly:
Why not rely on #!/usr/bin/env bash?
On macOS, /bin takes priority over /opt/homebrew/bin in the default PATH. env bash resolves to the system 3.2 unless Homebrew is explicitly prepended to PATH. Pinning the shebang is safer for scripts shared across team machines.
Option 2 — Rewrite without associative arrays (Bash 3.2 compatible)¶
Replace declare -A with a case function. Works on Bash 3.2, 5.x, and sh:
get_title() {
case "$1" in
container-runtime) echo "Container Runtime" ;;
language-runtimes) echo "Language Runtimes" ;;
components) echo "Components" ;;
esac
}
title="$(get_title "$folder")"
Why case instead of declare -A?
The case statement is a POSIX construct supported by all shell versions. It avoids any dependency on Bash version and works in scripts invoked with #!/bin/sh.
Verification¶
Expected:
Expected:
✅ Created: container-runtime/
✅ Created: language-runtimes/
...
🎉 All bootstrap folders initialized successfully.
Troubleshooting¶
Script still fails after installing Homebrew bash¶
Cause: #!/usr/bin/env bash still resolves to /bin/bash (3.2) because /bin precedes /opt/homebrew/bin in PATH.
Fix: Prepend Homebrew to PATH in your shell profile:
Or use the absolute shebang:
Verify which bash env resolves to:
Warning
Never delete or replace /bin/bash on macOS. System scripts and macOS internals depend on it. Always install a parallel Homebrew version instead.