Iterate and Check if a Bash Array contains a value

Shell Tips
January 27, 2020 | COMMENTS

Version 2 of GNU Bash added support for array variables, a.k.a one-dimensional indexed arrays (or lists). Since the version 4, came the support for associative arrays (a.k.a dictionaries or hashtables). Those features simplifies heavily how you can write your scripts and support more complex logics and use cases.

In this post we will review how to declare, iterate over, and check a value of an indexed arrays and associative arrays.

  1. Difference between Indexed Arrays and Associative Arrays
  2. How to declare a Bash Array
  3. Array Operations and How to loop over a Bash Array
  4. How to Check if a Bash Array contains a value

1. Difference between Indexed Arrays and Associative Arrays

First and foremost, you need to differentiate clearly the two types of arrays that can be used in bash. An indexed array is an array in which the keys (indexes) are ordered integers. You can think about it as an ordered list of items. Then, an associative array, a.k.a hash table, is an array in which the keys are represented by arbitrary strings.

2. How to declare a Bash Array

Indexed Array

You can create an Indexed Array on the fly or by using the built-in command declare.

$ myIndexedArray=(one two three)
$ echo ${myIndexedArray[*]}
one two three

$ myIndexedArray[5]='five'
$ echo ${myIndexedArray[*]}
one two three five

$ myIndexedArray[4]='four'
$ echo ${myIndexedArray[*]}
one two three four five

$ myIndexedArray+=('six')
$ echo ${myIndexedArray[*]}
one two three four five six

With the declare built-in command and the lowercase "-a" option, you would simply do the following:

$ declare -a mySecondIndexedArray
$ mySecondIndexedArray[0]='zero'
$ echo ${mySecondIndexedArray[*]}
zero

Associative Array

You cannot create an associative array on the fly. You can only use the declare built-in command with the uppercase "-A" option.

$ declare -A myAssociativeArray
$ myAssociativeArray[a]=123
$ myAssociativeArray[b]=456
$ myAssociativeArray+=([c]=789 [d]=012)
$ echo ${myAssociativeArray[*]}
012 789 456 123

⚠️ Do not confuse -a (lowercase) with -A (uppercase). It would silently fail. Indeed, declaring an Indexed array will accept subscript but will ignore it and treat the rest of your declaration as an Indexed Array, not an Associative Array.

👉 Read more about the common error "Bash Error: must use subscript when assigning associative array".

3. Array Operations and How to loop over a Bash Array

As we saw above, we can access all the value of an array using the * (wildcard) notation.

$ myDemoArray=(1 2 3 4 5)
$ echo ${myDemoArray[*]}
1 2 3 4 5

You can also use the @ (at) notation for the same effect.

$ echo ${myDemoArray[*]}
1 2 3 4 5

The difference between the two will arise when you try to loop over such array using quotes. The * notation will return all the elements of the array as a single result while the @ notation will return a value for each elements of the array. This becomes clear when performing a for loop on such variable.

$ myDemoArray=(1 2 3 4 5)
$ for value in "${myDemoArray[*]}"; do echo "$value"; done
1 2 3 4 5

$ myDemoArray=(1 2 3 4 5)
$ for value in "${myDemoArray[@]}"; do echo "$value"; done
1
2
3
4
5

When looping over an array it's often useful to access the keys of the array which can be done using the ! (bang) notation.

# Print Indexed Array Keys
$ for keys in "${!myDemoArray[@]}"; do echo "$keys"; done
0
1
2
3
4

# Print Associative Array Values
$ myAssociativeArray=([a]=123 [b]=456)
$ for value in "${myAssociativeArray[@]}"; do echo "$value"; done
456
123

# Print Associative Array Keys
$ myAssociativeArray=([a]=123 [b]=456)
$ for keys in "${!myAssociativeArray[@]}"; do echo "$keys"; done
b
a

# Iterate over key and value of an Associative Array
$ myAssociativeArray=([a]=123 [b]=456)
$ for key in "${!myAssociativeArray[@]}"
> do
>   echo -n "key  : $key, "
>   echo "value: ${myAssociativeArray[$key]}"
> done
key  : b, value: 456
key  : a, value: 123

Another useful aspect of manipulating Bash Arrays is to be able to get the count of elements in an array. You can get the size of an Array variable with the # (hashtag) notation.

$ myArray=(a b c d)
$ echo "myArray contain ${#myArray[*]} elements"
myArray contain 4 elements

$ myAssociativeArray=([a]=123 [b]=456)
$ echo "myAssociativeArray contain ${#myAssociativeArray[*]} elements"
myAssociativeArray contain 2 elements

4. How to Check if a Bash Array contains a value

In most cases, you can probably use the binary operator =~. The string to the right of the operator is considered a POSIX extended regular expression and matched accordingly. Though, this would not look for exact match, it is a regex.

$ myArray=(a b c d)
$ [[ ${myArray[*]} =~ 'a' ]] && echo 'yes' || echo 'no'
yes
$ [[ ${myArray[*]} =~ 'e' ]] && echo 'yes' || echo 'no'
no

# Return True even for partial match
$ myArray=(a1 b1 c1 d1 ee)
$ [[ ${myArray[*]} =~ 'a' ]] && echo 'yes' || echo 'no'
yes
$ [[ ${myArray[*]} =~ 'a1' ]] && echo 'yes' || echo 'no'
yes
$ [[ ${myArray[*]} =~ 'e' ]] && echo 'yes' || echo 'no'
yes
$ [[ ${myArray[*]} =~ 'ee' ]] && echo 'yes' || echo 'no'
yes
$ echo ${myAssociativeArray[*]}
456 123
$ [[ ${myAssociativeArray[*]} =~ 3 ]] && echo 'yes' || echo 'no'
yes
$ [[ ${myAssociativeArray[*]} =~ 123 ]] && echo 'yes' || echo 'no'
yes
$ [[ ${myAssociativeArray[*]} =~ 1234 ]] && echo 'yes' || echo 'no'
no

In order to account for an exact match, your regex pattern needs to account for it by adding extra space before and after the value like (^|[[:space:]])"VALUE"($|[[:space:]]).

$ [[ ${myAssociativeArray[*]} =~ (^|[[:space:]])"12"($|[[:space:]]) ]] && echo 'yes' || echo 'no' 
no
$ [[ ${myAssociativeArray[*]} =~ (^|[[:space:]])"123"($|[[:space:]]) ]] && echo 'yes' || echo 'no' 
yes

With Associative Arrays, you can extend the solution to test values with [[ -z "${myArray[$value]}" ]].

# delete previously set declaration of myArray and
# prevent the error `bash: myArray: cannot convert indexed to associative array`
unset myArray 

declare -A myArray=([one]=un [two]=deux [three]=trois)
$ echo ${myArray[*]}
deux trois un

$ for value in one two three four
> do
>     echo -n "$value is "
>     [[ -z "${myArray[$value]}" ]] && echo -n '*not* '
>     echo "a member of ( ${!myArray[*]} )"
> done
one is a member of ( two three one )
two is a member of ( two three one )
three is a member of ( two three one )
four is *not* a member of ( two three one )