Bash Arrays: Indexed and Associative Arrays
What You'll Learn
- The difference between bash indexed arrays and associative arrays
- The core operations: declaring, accessing, looping, and adding/removing elements
- How to avoid classic traps like missing quotes on
"${arr[@]}"and sparse arrays
Quick Summary
- Ordered list of values -> indexed array (
arr=(a b c)) - Key/value lookup table -> associative array (
declare -A mapis required) - Always double-quote
"${arr[@]}"when looping or expanding
Prerequisites (target environment)
- Shell: bash 4.0+ (associative arrays need 4.0+, negative indices need 4.3+)
sh/dashdo not support arrays. Use an explicit#!/bin/bashshebang
What is an array?
Conclusion: An array stores multiple values in one variable. Bash has two kinds: ordered indexed arrays and key-based associative arrays.
A plain shell variable holds a single value. When you need to handle several filenames or usernames together, cramming them into one space-separated string breaks as soon as a value contains a space. An array keeps each element as an independent, safely-quoted value.
Bash offers two array types:
- Indexed array: elements are addressed by integer indices starting at
0 - Associative array: elements are looked up by an arbitrary string key (like a hash or dictionary)
How do you use indexed arrays?
Conclusion: Declare with
arr=(a b c), read one element with${arr[0]}, and all elements with"${arr[@]}". Omitting the index refers to the first element.
Declaring and assigning
# Declare all at once fruits=(apple banana cherry) # Assign by index fruits[3]=durian # Append to the end (+= operator) fruits+=(elderberry fig)
Accessing
echo "${fruits[0]}" # apple (first element)
echo "${fruits[-1]}" # fig (last, bash 4.3+)
echo "${fruits[@]}" # all elements, space-separated
echo "${#fruits[@]}" # element count (5)
echo "${!fruits[@]}" # all indices (0 1 2 3 4 5)Referencing an array without an index, like $fruits, is the same as ${fruits[0]} (first element only). Watch for this mix-up when you meant to use every element.
Slicing (taking a subset)
echo "${fruits[@]:1:2}" # banana cherry (2 elements from index 1)How do you use associative arrays?
Conclusion: Always declare an associative array with
declare -Abefore use. Skip it and bash treats it as an indexed array, collapsing keys to 0 and corrupting the data.
An associative array lets you use arbitrary strings as keys. The key difference from indexed arrays is that you must declare it with declare -A first.
declare -A capital # forget this and it breaks
capital[japan]=Tokyo
capital[france]=Paris
capital["united states"]=Washington
echo "${capital[japan]}" # Tokyo
echo "${!capital[@]}" # all keys (order is undefined)
echo "${capital[@]}" # all values (order is undefined)
echo "${#capital[@]}" # element count (3)Without declare -A, writing capital[japan]=Tokyo makes bash evaluate japan arithmetically as 0 and assign to index 0 of an indexed array. Associative arrays require the declaration.
Associative arrays do not preserve order. Neither insertion order nor key order is guaranteed on output, so pipe through sort if you need ordering.
How do you loop over an array?
Conclusion: Loop values with
for x in "${arr[@]}", and keys/indices withfor k in "${!arr[@]}". Both require double quotes.
# Process each value in turn
for f in "${fruits[@]}"; do
echo "fruit: $f"
done
# Loop keys (associative) and look up values
for country in "${!capital[@]}"; do
echo "$country -> ${capital[$country]}"
doneDropping the quotes, as in for f in ${fruits[@]}, causes word splitting on elements that contain spaces: a single element apple pie splits into apple and pie. Always use "${arr[@]}" to keep values intact.
What is the difference between "${arr[@]}" and "${arr[*]}"?
Conclusion:
[@]expands each element as a separate word;[*]joins them into one string using the first character of IFS. Use[@]for loops.
| Notation | Expands to | Typical use |
|---|---|---|
"${arr[@]}" |
one separate word per element | loops, passing args |
"${arr[*]}" |
a single string joined by IFS[0] | display, stringify |
arr=(one two three)
printf '[%s]\n' "${arr[@]}" # [one] [two] [three] on separate lines
printf '[%s]\n' "${arr[*]}" # [one two three] on one line
# Join with a custom separator
IFS=,
echo "${arr[*]}" # one,two,three
unset IFSHow do you add or remove elements?
Conclusion: Append with
arr+=(...)and delete withunset 'arr[2]'. Note that unsetting from an indexed array leaves it sparse - indices are not renumbered.
arr=(a b c d)
arr+=(e) # append -> a b c d e
unset 'arr[1]' # delete index 1 (b)
echo "${arr[@]}" # a c d e
echo "${!arr[@]}" # 0 2 3 4 <- index 1 is missing (not renumbered)After unset, the array is no longer contiguous (a sparse array). The count ${#arr[@]} no longer matches the highest index, so any loop assuming indices 0..N-1 breaks. Reassign to renumber:
arr=("${arr[@]}") # reindex to 0,1,2...What are the common pitfalls?
Conclusion: Arrays are bash-only, quoting is mandatory, and associative arrays need declare -A. Miss any of these and you get a classic failure.
Don't do this / easy to get wrong
- Using arrays under
#!/bin/sh-> syntax error indashetc. Use#!/bin/bash for x in ${arr[@]}(no quotes) -> elements with spaces get split- Forgetting
declare -Afor an associative array -> keys collapse to 0 and overwrite each other - Writing
$arrforarr[0]-> you get only the first element, not all
To capture command output into an array, mapfile (also called readarray) reads safely line by line.
mapfile -t lines < <(ls -1) # each line becomes one element of lines
echo "${#lines[@]}" # number of linesThe -t option strips the trailing newline from each line; without it, elements keep a \n.