The day a script needs a list — servers, files, options — bash arrays arrive. They are bash's real data structure, and most of their reputation for awkwardness comes from using them the way you would use a string. Handled with "${arr[@]}", they are dependable.

Creating and reading #

Index, append, count
$ servers=(web1 web2 db1)
$ echo "${servers[0]}"
web1
$ servers+=(db2)
$ echo "${#servers[@]}"
4

Parentheses make the array; spaces separate elements — so elements with spaces arrive intact, no escaping gymnastics. Appending is +=(...), counting is ${#arr[@]}.

The one true iteration #

Loop safely over every element
$ for s in "${servers[@]}"; do ping -c1 "$s"; done

The quotes around "${servers[@]}" are the whole note: quoted, each element stays one word even with spaces; unquoted, the array explodes back into a whitespace-split string. The same law governs "${@}" for script arguments — same machinery.

Slices and keys #

FormResult
${arr[@]:1:2}Elements 1 and 2 (offset, length)
${arr[-1]}Last element
${!arr[@]}All indices — or keys, for associative
${arr[@]/*.old/*.new}Pattern-replaced copy
unset 'arr[2]'Remove one element (index stays sparse)

Associative arrays — declared with declare -A — map keys to values, and quietly replace a lot of case-statements:

A lookup table instead of a case ladder
declare -A env_url=(
  [dev]="https://dev.aospix.com"
  [prod]="https://aospix.com"
)
echo "${env_url[$environment]:-unknown env}"

The :- default keeps unknown keys from expanding to empty — the strict-mode friendly form.

Where arrays beat strings #

Any time a list is passed around — building a command with optional flags, collecting failed hosts, holding a find result — an array beats a space-joined string because it never re-splits:

Building a command with optional parts
args=(-a --delete)
[[ $verbose == 1 ]] && args+=(-v)
rsync "${args[@]}" src/ dst/

Elements and expansion share the quoting law; building commands from arrays pairs with xargs for the argument-heavy cases. The scripting series continues.