Bash Arrays and Associative Arrays
Master bash indexed and associative arrays: declaration, element access, looping, mapfile, namerefs, and practical patterns for real scripting work.
Before you start
- ▸Bash 4.0 or later (bash --version to confirm; required for associative arrays)
- ▸Basic familiarity with bash variables and quoting rules
- ▸A text editor and a terminal to test scripts
Bash arrays let you group related values under a single variable name and manipulate them with built-in syntax. Indexed arrays work like numbered lists; associative arrays (bash 4+) work like dictionaries or hash maps. Both are essential for writing scripts that handle real-world data without spawning subshells or abusing string splitting.
Indexed Arrays
Declaration and Assignment
You can declare an array explicitly or let bash infer it from the assignment syntax.
# Explicit declaration
declare -a fruits
# Inline assignment (most common)
fruits=(apple banana cherry)
# Assign individual elements by index
fruits[3]=date
fruits[10]=elderberry # sparse arrays are fine
Indexes start at 0. Sparse arrays (gaps in the index sequence) are valid — bash does not allocate memory for the missing slots.
Reading Elements
echo "${fruits[0]}" # apple
echo "${fruits[@]}" # all elements, each as a separate word
echo "${fruits[*]}" # all elements joined by IFS (usually a space)
echo "${#fruits[@]}" # number of elements
echo "${!fruits[@]}" # all indexes (useful for sparse arrays)
Always quote ${fruits[@]} when passing it to another command or loop — unquoted, word splitting will break elements that contain spaces.
Slicing and Substring Expansion
files=(a.log b.log c.log d.log e.log)
# Elements 1 and 2 (offset 1, length 2)
echo "${files[@]:1:2}" # b.log c.log
# From index 2 to end
echo "${files[@]:2}" # c.log d.log e.log
Adding and Removing Elements
# Append one or more elements
fruits+=(fig grape)
# Remove element at index 2 (leaves a gap in a sparse array)
unset 'fruits[2]'
# Re-index to close gaps
fruits=("${fruits[@]}")
After unset, the index is gone but the array is not re-numbered automatically. Re-assigning via ("${fruits[@]}") collapses the gaps into a clean 0-based sequence.
Associative Arrays
Declaration
Associative arrays require an explicit declare -A. This is a bash 4.0+ feature; macOS ships bash 3.2 by default, so check your version with bash --version if targeting mixed environments.
declare -A config
config[host]=db.example.com
config[port]=5432
config[user]=appuser
# Or all at once
declare -A config=([host]=db.example.com [port]=5432 [user]=appuser)
Reading Associative Array Data
echo "${config[host]}" # db.example.com
echo "${!config[@]}" # all keys (order not guaranteed)
echo "${config[@]}" # all values
echo "${#config[@]}" # number of pairs
Key order in associative arrays is not preserved in bash. If order matters, maintain a separate indexed array of keys.
Checking for Key Existence
if [[ -v config[port] ]]; then
echo "port is set: ${config[port]}"
else
echo "port key missing"
fi
-v tests whether the variable (or array element) is set. It was introduced in bash 4.2. Avoid testing with -z alone — it cannot distinguish an unset key from a key whose value is an empty string.
Looping Over Arrays
Basic For Loop
servers=(web1 web2 db1 db2)
for server in "${servers[@]}"; do
echo "Pinging $server"
ping -c1 -W1 "$server" &>/dev/null && echo " up" || echo " down"
done
Loop With Index
for i in "${!servers[@]}"; do
printf "[%d] %s\n" "$i" "${servers[$i]}"
done
Looping Over Associative Arrays
declare -A colours=([red]='#FF0000' [green]='#00FF00' [blue]='#0000FF')
for key in "${!colours[@]}"; do
printf "%-10s %s\n" "$key" "${colours[$key]}"
done
C-Style Loop for Indexed Arrays
for (( i=0; i<${#servers[@]}; i++ )); do
echo "Server $i: ${servers[$i]}"
done
Common Patterns
Building an Array from Command Output
# mapfile (readarray) reads lines into an indexed array — bash 4.0+
mapfile -t log_files < <(find /var/log -name '*.log' -maxdepth 2)
echo "Found ${#log_files[@]} log files"
for f in "${log_files[@]}"; do
echo "$f"
done
mapfile -t strips the trailing newline from each line. This is far safer than IFS splitting because it handles filenames with spaces correctly.
Deduplicating with an Associative Array
input=(alpha beta alpha gamma beta delta)
declare -A seen
unique=()
for item in "${input[@]}"; do
if [[ ! -v seen[$item] ]]; then
seen[$item]=1
unique+=("$item")
fi
done
echo "${unique[@]}" # alpha beta gamma delta
Passing Arrays to Functions
Bash does not pass arrays by value. Use a nameref (bash 4.3+) or expand the array when calling the function.
print_all() {
local -n arr=$1 # nameref: arr is an alias for the caller's variable
for elem in "${arr[@]}"; do
echo " - $elem"
done
}
colours=(red green blue)
print_all colours
Array as a Stack
stack=()
# push
stack+=(item1)
stack+=(item2)
stack+=(item3)
# pop
last_index=$(( ${#stack[@]} - 1 ))
top=${stack[$last_index]}
unset 'stack[$last_index]'
echo "Popped: $top" # item3
Expansion Tricks
words=(hello world foo bar)
# Uppercase all elements (bash 4.0+)
echo "${words[@]^^}" # HELLO WORLD FOO BAR
# Substring replacement across all elements
echo "${words[@]/o/0}" # hell0 w0rld f00 bar
# Count characters in one element
echo "${#words[1]}" # 5 (length of 'world')
Verification
After writing an array-heavy script, use bash -x to trace execution and confirm elements are expanding as expected:
bash -x your_script.sh 2>&1 | head -40
You can also dump the entire array state with declare -p:
declare -p fruits
# Output resembles: declare -a fruits=([0]="apple" [1]="banana" [3]="date")
Troubleshooting
Script fails with "declare: -A: invalid option"
Your bash is older than 4.0. On macOS, install a current bash via Homebrew (brew install bash) and update the shebang to #!/usr/bin/env bash. On Linux this is rarely an issue with any current LTS release.
Loop processes only one word of a multi-word element
You forgot to quote ${array[@]}. Always write "${array[@]}" (with double quotes) in loops and command arguments.
unset leaves gaps; length looks wrong
${#array[@]} returns the count of set elements, not the highest index plus one. For a sparse array these differ. Re-index with array=("${array[@]}") if you need a contiguous sequence.
Associative array keys with spaces
Quoting the key is required: config["my key"]="value". Unquoted keys with spaces cause a syntax error.
Frequently asked questions
- What is the difference between ${array[@]} and ${array[*]}?
- When double-quoted, "${array[@]}" expands to separate words (one per element), while "${array[*]}" joins all elements into a single word using the first character of IFS. Use [@] in loops and when passing to commands.
- Can I use bash arrays in a POSIX sh script?
- No. Arrays are a bash (and ksh/zsh) extension. POSIX sh has no array support. If your shebang is #!/bin/sh, array syntax will cause a syntax error on strict sh implementations.
- How do I sort an array?
- Pipe the expansion through sort and read it back with mapfile: mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort). There is no built-in sort for arrays.
- Why is the order of keys in an associative array unpredictable?
- Bash stores associative array keys in a hash table with no guaranteed insertion order. If you need ordered keys, maintain a separate indexed array listing them in the desired sequence.
- How do I check if a value exists in an indexed array?
- Bash has no built-in contains test. A common pattern is to loop and compare, or load elements as keys of an associative array and use [[ -v assoc[$value] ]] for O(1) lookup.
Related guides
Bash Functions and Variable Scoping
Master Bash function scoping with local variables, source-based libraries, correct use of return codes, and array passing techniques including namerefs.
Bash Loops: for, while and until
Learn all three Bash loop types — for, while, and until — with practical, copy-paste examples covering file iteration, counting, polling, and safe line reading.
Bash Scripting for Beginners
Learn Bash scripting from scratch: shebang lines, variables, conditionals, loops, and arguments, plus a real backup script to tie it all together.
Build a Real Command-Line Tool in Shell
Build a proper CLI tool in Bash with strict mode, long/short argument parsing, --help, usage(), and clean packaging via install and a Makefile.