Associative arrays (also called hash maps or dictionaries) let you map arbitrary string keys to values. Introduced in bash 4.0, they eliminate the need for complex awk or grep workarounds when you need key-value lookups inside a script.
bash --version. On macOS install bash via Homebrew: brew install bash. On Linux, bash 4+ is standard since ~2009.You must use declare -A to create an associative array. Without it, bash treats it as an indexed array and silently discards your string keys.
# ── Declare (mandatory for associative arrays) ────────────
declare -A colors
declare -A db_config
# ── Populate after declaration ────────────────────────────
colors["red"]="#ff0000"
colors["green"]="#00ff00"
colors["blue"]="#0000ff"
colors["orange"]="#e0701a"
# ── Declare and populate in one statement ─────────────────
declare -A db_config=(
["host"]="prod-db-01"
["port"]="3306"
["name"]="appdb"
["user"]="app_user"
)
# ── Access a value by key ─────────────────────────────────
echo "Red : ${colors[red]}"
echo "Host : ${db_config[host]}"
echo "Port : ${db_config[port]}"
# Keys with spaces — quote the key
declare -A server_info
server_info["app server"]="10.0.1.10"
server_info["db server"]="10.0.1.20"
echo "App: ${server_info["app server"]}"
declare -A ports=(
["ssh"]=22
["http"]=80
["https"]=443
["mysql"]=3306
["postgres"]=5432
)
# ── All keys ──────────────────────────────────────────────
echo "Keys : ${!ports[@]}"
# ── All values ────────────────────────────────────────────
echo "Values: ${ports[@]}"
# ── Count of entries ──────────────────────────────────────
echo "Count : ${#ports[@]}"
# ── Iterate over keys ─────────────────────────────────────
echo "All services:"
for svc in "${!ports[@]}"; do
echo " ${svc} → ${ports[$svc]}"
done
# ── Iterate over values only ──────────────────────────────
for port in "${ports[@]}"; do
echo " port: ${port}"
done
# ── Sorted output (keys are unordered in bash) ────────────
for svc in $(echo "${!ports[@]}" | tr ' ' '\n' | sort); do
printf " %-12s %s\n" "${svc}" "${ports[$svc]}"
done
sort as shown above. Never rely on the order of ${!map[@]}.declare -A config=(
["env"]="staging"
["debug"]="false"
["workers"]="4"
)
# ── UPDATE — simply reassign ──────────────────────────────
config["env"]="production"
config["workers"]="16"
# ── ADD new key ───────────────────────────────────────────
config["timeout"]="30"
config["log_level"]="INFO"
# ── CHECK if key exists ───────────────────────────────────
if [[ -v config["env"] ]]; then
echo "env key exists: ${config[env]}"
fi
# Alternative check (works in bash 3 too)
if [[ -n "${config[env]+set}" ]]; then
echo "env is set"
fi
# ── DELETE a key ──────────────────────────────────────────
unset 'config[debug]' # Quote to prevent glob expansion
# ── DELETE entire array ───────────────────────────────────
unset config
# ── Safe access with default ──────────────────────────────
declare -A settings
timeout="${settings[timeout]:-60}" # Default 60 if key missing
echo "Timeout: ${timeout}"
One of the most powerful uses of associative arrays is counting occurrences — something that would otherwise require a temporary file or complex pipeline.
#!/usr/bin/env bash
# count_http_codes.sh — Count HTTP status codes in access log
declare -A code_count
while read -r line; do
# Extract HTTP status code (field 9 in Apache/Nginx combined log)
code=$(echo "${line}" | awk '{print $9}')
[[ "${code}" =~ ^[0-9]+$ ]] || continue
(( code_count["${code}"]++ ))
done < /var/log/nginx/access.log
echo "HTTP Status Code Summary"
echo "─────────────────────────"
for code in $(echo "${!code_count[@]}" | tr ' ' '\n' | sort); do
printf " HTTP %-6s %d requests\n" "${code}" "${code_count[$code]}"
done
#!/usr/bin/env bash
# deploy.sh — Environment-aware deployment using lookup table
declare -A DB_HOSTS=(
["dev"]="dev-db-01.internal"
["staging"]="stg-db-01.internal"
["production"]="prod-db-01.internal"
)
declare -A APP_PORTS=(
["dev"]="8080"
["staging"]="8081"
["production"]="80"
)
declare -A REPLICAS=(
["dev"]="1"
["staging"]="2"
["production"]="8"
)
ENV="${1:-dev}"
# Validate environment
if [[ ! -v DB_HOSTS["${ENV}"] ]]; then
echo "ERROR: Unknown environment '${ENV}'" >&2
echo "Valid: ${!DB_HOSTS[*]}" >&2
exit 1
fi
echo "Deploying to: ${ENV}"
echo " DB Host : ${DB_HOSTS[$ENV]}"
echo " Port : ${APP_PORTS[$ENV]}"
echo " Replicas : ${REPLICAS[$ENV]}"
Bash cannot pass associative arrays directly to functions. The standard pattern is to pass the array name as a string and use a nameref (declare -n) inside the function — available from bash 4.3+.
#!/usr/bin/env bash
# Passing associative arrays via nameref (bash 4.3+)
print_map() {
local -n map="${1}" # -n creates a nameref
local title="${2:-Map}"
echo "── ${title} ──"
for key in $(echo "${!map[@]}" | tr ' ' '\n' | sort); do
printf " %-15s = %s\n" "${key}" "${map[$key]}"
done
}
merge_maps() {
local -n source="${1}"
local -n target="${2}"
for key in "${!source[@]}"; do
target["${key}"]="${source[$key]}"
done
}
declare -A defaults=(["timeout"]="30" ["retries"]="3" ["log"]="INFO")
declare -A overrides=(["timeout"]="60" ["debug"]="true")
merge_maps overrides defaults
print_map defaults "Final Config"
grep lookups in a loop, maintaining parallel arrays with matching indexes, or using temp files to store key-value pairs. An associative array is cleaner, faster, and entirely in-memory.