Bash: declare in depth, Part 3: arrays

Bash supports two types of arrays: numerically indexed arrays and associative arrays (i.e. indexed by a string).

To create an array, pass -a or -A (-A for associative arrays) to declare (or any of its variable-defining cousins like local and readonly).

Some people think that needing arrays is the point where you should switch to another language. In some contexts this can certainly be true (e.g. nobody on the team knows how arrays work in Bash), but comes at a cost: shells are still the most concise form for starting programs and connecting them together. Switching to a language like Python requires setting up libraries on all target systems and the resulting code for orchestrating processes still looks very clumsy compared to a shell script.

Arrays: a reference

Under the hood arrays in Bash are actually implemented as linked lists. This is different from what most dynamic languages call "arrays", so be aware of that if you ever need to build large arrays.

Both arrays and associative arrays share a lot of functionality:

  • create them either with declare or local
  • list all keys in the array: ${!array[@]}
  • list all elements in the array: ${array[@]}
  • remove an element with unset: unset array[index]
  • get a specific element: ${array[elem]}
  • get the number of elements: ${#array[@]}

There are some important differences though:

Special syntax for creating array (x=()) will always create a numerically indexed array:

$ x=()
$ declare -p x
declare -a x=()

The index of numerical arrays is always evaluated using Bash's rules for arithmetic:

$ i=0
$ x[i+1]=2
$ declare -p x 
declare -a x=([1]="2")

Since the index of a numerically-index array can be calculated, bash also offers syntax for appending to the array:

$ x+=(three)
$ declare -p x
declare -a x=([1]="2" [2]="three")

Arrays as linked lists

Arrays being implemented as linked lists can lead to some surprising behavior.
When removing elements from an array indices are not renumbered.

$ declare -a words=(a list of words)
$ declare -p words
declare -a words=([0]="a" [1]="list" [2]="of" [3]="words")
$ unset words[2]
$ declare -p words
declare -a words=([0]="a" [1]="list" [3]="words")

Arrays and export

Arrays cannot be exported to subshells, but Bash is not telling you this, choosing to silently fail instead:

$ declare -a numbers=(one two three)
$ export numbers # doesn't fail
$ bash -c 'declare -p numbers'
bash: line 1: declare: numbers: not found

While this is not ideal, exporting arrays is actually possible by serializing them first (with declare -p!).

$ export state=$(declare -p numbers)
$ bash -c 'eval "$state"; declare -p numbers'
declare -a numbers=([0]="one" [1]="two" [2]="three")

You'll only receive email when they publish something new.

More from Dario Hamidi
All posts