Indexed and associative arrays

Bash · Practical · 12 min

Arrays are the only safe way to hold lists of file names or arguments that may contain spaces.

An indexed array is created with arr=(a "b c" d) and grows with arr+=(e). Index from 0: ${arr[1]}. The essential expansions:

  • "${arr[@]}" every element as a separate word (always quote it),
  • ${#arr[@]} number of elements, ${!arr[@]} the indices,
  • "${arr[@]:1:2}" a slice.

Arrays can be sparse: after unset 'arr[0]' the indices are 1 2 3, so loop over elements or over ${!arr[@]} rather than assuming 0..n-1.

Associative arrays (bash 4+) must be declared: declare -A port=([http]=80 [https]=443). Set with port[ssh]=22, read with ${port[ssh]}, list keys with "${!port[@]}". Key order is not defined, so sort if you need stable output. Test for a key with [[ -v port[ssh] ]].

Arrays shine for building command lines: args=(--verbose); [[ $dry ]] && args+=(--dry-run); rsync "${args[@]}" src/ dst/ passes each option as exactly one argument, which string concatenation can not do safely.

To read lines of a file or command into an array use mapfile -t lines < file (also called readarray). Bash arrays are one-dimensional and cannot be exported to child processes.

Example

cd "$(mktemp -d)"
fruits=(apple "star fruit" kiwi)
fruits+=(mango)
echo "${fruits[1]}"          # star fruit
echo "${#fruits[@]}"         # 4
for f in "${fruits[@]}"; do printf '<%s>' "$f"; done; echo
# <apple><star fruit><kiwi><mango>
echo "${fruits[@]:1:2}"      # star fruit kiwi
unset 'fruits[0]'
echo "${!fruits[@]}"         # 1 2 3  (sparse!)
declare -A port=([http]=80 [https]=443)
port[ssh]=22
echo "${port[https]}"        # 443
[[ -v port[ftp] ]] || echo "no ftp"    # no ftp
for k in "${!port[@]}"; do echo "$k=${port[$k]}"; done   # order not guaranteed
printf 'x\ny\n' > two.txt
mapfile -t lines < two.txt
echo "${#lines[@]} lines, first=${lines[0]}"   # 2 lines, first=x

Remember

  • "${arr[@]}" expands each element as one word; always quote it
  • declare -A for key/value maps; keys come back in no particular order
  • Build command lines in arrays, then run "${cmd[@]}"

Watch out for

  • Writing ${arr} or $arr, which gives only the first element.

Check yourself

  1. After `a=(x y z); unset 'a[1]'`, what does ${#a[@]} print?
  2. Which expansion keeps 'star fruit' as a single word when passed to a command?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.gnu.org/software/bash/manual/bash.html#Arrays