Arrays in Bash
Bash supports two kinds of arrays: indexed arrays, where elements are accessed by a numeric index starting at 0, and associative arrays, where elements are accessed by an arbitrary string key. Arrays solve a real problem plain variables can't: holding a genuine list of values — filenames, hostnames, key-value configuration — without resorting to fragile string concatenation with delimiters. Associative arrays were introduced in Bash 4.0 (2009), so scripts targeting strict POSIX sh or very old Bash (like macOS's stock Bash 3.2) cannot rely on them, but indexed arrays have been available since Bash 2.0 and are safe almost everywhere Bash itself is.
Cricket analogy: An indexed array is like a Test batting order numbered 1 to 11, while an associative array is like a scorecard keyed by player name (Kohli, Root) instead of slot number; associative arrays only work on 'modern rules' Bash 4.0+, not on an old-school scoring sheet.
Indexed arrays: creating and accessing
An indexed array is created with arr=(one two three) or by assigning individual elements like arr[0]=one. Indices don't need to be contiguous — you can set arr[5]=x on an otherwise empty array, leaving a sparse array. Accessing a single element uses ${arr[i]}; accessing all elements uses ${arr[@]} (each element as a separate word, the form you almost always want) or ${arr[*]} (all elements joined into one string using IFS). ${#arr[@]} gives the number of elements, and ${!arr[@]} gives the list of indices in use, which matters for sparse arrays.
Cricket analogy: Building arr=(one two three) is like naming your top three batsmen at once, while arr[5]=x is like slotting a specialist bowler straight into batting position 6, leaving gaps; ${arr[@]} reads out each player separately while ${arr[*]} reads the whole XI as one joined string.
servers=("web01" "web02" "db01")
echo "${servers[0]}" # web01
echo "${servers[@]}" # web01 web02 db01
echo "${#servers[@]}" # 3 (element count)
# Append an element
servers+=("cache01")
# Iterate safely, quoting the expansion
for host in "${servers[@]}"; do
ping -c1 -W1 "$host" &>/dev/null && echo "$host: up" || echo "$host: down"
done
# Slice: elements 1 through 2 (offset 1, length 2)
echo "${servers[@]:1:2}" # web02 db01
# Remove an element by index
unset 'servers[1]'
echo "${servers[@]}" # web01 db01 cache01 (index 1 gap remains)Associative arrays
Associative arrays require an explicit declare -A name before use — Bash cannot infer that a bare name=() should be associative rather than indexed. Keys are arbitrary strings, making associative arrays ideal for configuration-style lookups: mapping hostnames to IP addresses, environment names to URLs, or option flags to their descriptions. Iteration order for associative arrays is unspecified, so if order matters, sort the keys explicitly before iterating.
Cricket analogy: declare -A is like explicitly declaring a squad list keyed by player name rather than batting slot; Bash won't guess you meant a name-keyed squad sheet, so mapping venues to pitch conditions needs that explicit declaration, and player-order iteration isn't guaranteed unless you sort names first.
declare -A env_urls
env_urls[dev]="https://dev.example.com"
env_urls[staging]="https://staging.example.com"
env_urls[production]="https://example.com"
target="staging"
echo "Deploying to ${env_urls[$target]}"
# Iterate keys in sorted order for deterministic output
for env in $(printf '%s\n' "${!env_urls[@]}" | sort); do
echo "$env -> ${env_urls[$env]}"
done
# Check if a key exists
if [[ -v env_urls[qa] ]]; then
echo "qa is configured"
else
echo "qa is not configured"
fideclare -a explicitly declares an indexed array and declare -A an associative array; using plain declare (or no declare at all) on an assignment like name=(a b c) always creates an indexed array, even if you intended keys. This is a common source of confusion for developers coming from languages where {}-style literals imply a dictionary/map.
${arr[@]} and ${arr[*]} behave identically until you look closely at quoting: "${arr[@]}" expands to each element as a separate, correctly quoted word (safe for elements with spaces), while "${arr[*]}" joins all elements into a single string separated by the first character of IFS. Using ${arr[*]} (or worse, an unquoted ${arr[@]}) in a for loop over elements containing spaces will silently misbehave, splitting or merging elements incorrectly.
Common patterns: building arrays from command output
A frequent need is turning command output (like a list of files or lines) into an array. mapfile (also called readarray) reads lines from stdin directly into an indexed array, one element per line, and is the safest modern approach — far preferable to the older, riskier pattern of arr=($(command)), which is subject to word splitting and glob expansion on every element.
Cricket analogy: mapfile is like a scorer transcribing each ball-by-ball commentary line straight into a numbered log, one entry per delivery; the older arr=($(command)) approach is riskier, like copying scores by hand and accidentally splitting a two-word player name into separate entries.
# Safe: read each line of output into an array
mapfile -t running_containers < <(docker ps --format '{{.Names}}')
echo "Found ${#running_containers[@]} running containers"
for c in "${running_containers[@]}"; do
echo " - $c"
done
# Split a delimited string into an array
IFS=':' read -ra path_dirs <<< "$PATH"
for dir in "${path_dirs[@]}"; do
echo "$dir"
done- Indexed arrays use numeric indices (
arr=(a b c)); associative arrays need explicitdeclare -Aand use string keys. "${arr[@]}"(quoted, with @) expands each element as a separate word — the form to use in loops.${#arr[@]}returns the element count;${!arr[@]}returns the list of indices/keys in use.- Associative arrays are Bash 4.0+ only, unavailable on older Bash (e.g. macOS's stock 3.2) or POSIX sh.
mapfile -t arr < <(command)is the safe modern idiom for turning command output into an array, avoiding word-splitting pitfalls.- Associative array iteration order is unspecified — sort
"${!arr[@]}"explicitly if deterministic order matters.
Practice what you learned
1. What is required before you can use an associative array in Bash?
2. What is the safest way to iterate over an array's elements in a for loop, preserving elements that may contain spaces?
3. Why is `mapfile -t arr < <(command)` generally preferred over `arr=($(command))`?
4. What does `${#servers[@]}` return for an array `servers=("web01" "web02" "db01")`?
5. What is true about the iteration order of an associative array's keys in Bash?
Was this page helpful?
You May Also Like
Loops in Bash (for, while, until)
Learn Bash's three loop constructs — for, while, and until — plus loop control with break and continue, to iterate over lists, files, and command output safely.
Variables and Quoting in Bash
Understand how Bash stores and expands variables, and why quoting rules — single, double, and unquoted — are critical to writing scripts that don't break on real-world input.
Functions in Bash
Learn how to define reusable Bash functions, pass arguments and return values, manage variable scope with local, and organize larger scripts around them.
Command-Line Arguments and getopts
Learn how Bash scripts receive positional parameters, how to shift and iterate over them, and how to build proper option parsing with getopts.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsAdvanced Bash Scripting Study Notes
Bash · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics