Bash: declare in depth, Part 3: arrays
October 16, 2021•450 words
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
orlocal
- 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")