100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Bash

Arrays in Bash

Learn how to create, index, and iterate indexed and associative arrays in Bash, and how to use them to write scripts that handle lists of data correctly.

Bash Scripting In DepthIntermediate10 min readJul 9, 2026
Analogies

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.

bash
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.

bash
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"
fi

declare -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.

bash
# 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 explicit declare -A and 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

Was this page helpful?

Topics covered

#Bash#LinuxShellScriptingStudyNotes#DevOps#ArraysInBash#Arrays#Indexed#Creating#Accessing#DataStructures#StudyNotes#SkillVeris