Bash Bonanza Part 4: Arrays

Welcome to the fourth part of the Bash Bonanza series!

In the previous entry, we discussed how to use functions in Bash, and finished off with a spooky warning about arrays, and how they will not work with the techniques discussed so far.

Today we will explore that further.

Array Syntax

Bash has two types of arrays - indexed arrays (standard array) and key-value associative arrays (hash).

Array declaration

# The following will create an indexed array
$ declare -a ARRAY_NAME

# The following will create an associative array
$ declare -A ARRAY_NAME

Note that there are other ways to create indexed arrays, but you must use the declare syntax to create associative arrays. To avoid confusion, let's use this syntax for both.

Note that declare will automatically make the variable local if defined in a function.

Array initialization

This is done with parentheses for both types of arrays. For indexed arrays, keys are surrounded with square brackets to seperate them from from the value.

# Indexed array
$ declare -a INDEXED_ARRAY=('arr' 'super *dangerous* name!' 'hello')

# Associative array
$ declare -A ASSOCIATIVE_ARRAY=(['foo']='bar' ['hate']='no' ['love life']='yes')

For both types of arrays, referencing an element is done with the square brackets syntax.

Array element access and assignment

These are both done with the square brackets as follows.

# Recall that "${VARIABLE_NAME}" is how you would refer to an standard variable. The only difference here are the square brackets.

$ declare -a INDEXED_ARRAY=('arr' 'super *dangerous* name!' 'hello')
$ echo "${INDEXED_ARRAY[1]}"
> super *dangerous* name!
$ INDEXED_ARRAY[1]='My name is '"'"'James Bond'"'"''
$ echo "${INDEXED_ARRAY[1]}"
> My name is 'James Bond'

$ declare -A ASSOCIATIVE_ARRAY=(['foo']='bar' ['hate']='no' ['love life']='yes')
$ echo "${ASSOCIATIVE_ARRAY['love life']}"
> yes
$ ASSOCIATIVE_ARRAY['foo']='I did not eat all the pies'
$ echo "${ASSOCIATIVE_ARRAY['foo']}"
> I did not eat all the pies

Special indices

You can also use '@' and '*' inside square brackets. These will output the whole array. The difference is that '*' will output the array as a single string, while '@' will output each individual word separately.

$ declare -a INDEXED_ARRAY=('arr' 'super *dangerous* name!' 'hello')

$ printf '%s\n' "${INDEXED_ARRAY[*]}"
> arr super *dangerous* name! hello

$ printf '%s\n' "${INDEXED_ARRAY[@]}"
> arr
> super *dangerous* name!
> hello

Accessing keys

This can be done using the above special indices with the addition of an exclamation mark as follows.

$ declare -a INDEXED_ARRAY=('arr' 'super *dangerous* name!' 'hello')

# note the additional exclamation mark
$ printf '%s\n' "${!INDEXED_ARRAY[*]}"
> 0 1 2

$ printf '%s\n' "${!INDEXED_ARRAY[@]}"
> 0
> 1
> 2
Passing arrays to functions

This, as hinted before, is unfortunately more tricky than it should be.

The difficulty occurs because unlike in many other languages, referring the array variable will return the first element of the array instead of the whole array.

$ declare -a INDEXED_ARRAY=('arr' 'super *dangerous* name!' 'hello')
$ echo "${INDEXED_ARRAY}"
> arr

As a result, you cannot simply refer to the variable and pass it around functions. Instead, you will have to pass around the array name.

For this to work correctly, we will have to use the concept of an indirect reference in bash - basically fetching the value of a value. This is done by preceding the variable name with an exclamation mark (not to be confused with the exclamation mark to show indices - that is, in fact, an exception!).

$ declare -a INDEXED_ARRAY=('arr' 'super *dangerous* name!' 'hello')

# this is just a string
$ ARRAY_REFERENCE='INDEXED_ARRAY[@]'

# "${!ARRAY_REFERENCE}" will first get the value of ARRAY_REFERENCE, and then get *its* value
# "${!ARRAY_REFERENCE}" -> "${INDEXED_ARRAY[@]} -> the array elements, as we have seen before
$ printf '%s\n' "${!ARRAY_REFERENCE}"
> arr
> super *dangerous* name!
> hello

Using this, we can do the following - it even works with multiple arrays. Shout out to Ken Bertelson for this trick!

main()  
{
  declare -a INDEXED_ARRAY=('arr' 'super *dangerous* name!' 'hello')
  do_cool_stuff 'INDEXED_ARRAY[@]'
}

# $1 - array name
do_cool_stuff()  
{
  local array_name="$1"; shift

  # get the array elements and stick them in a new array
  declare -a MY_INDEXED_ARRAY=("${!array_name}")

  printf '%s\n' "${MY_INDEXED_ARRAY[@]}"
}

main

> arr
> super *dangerous* name!
> hello
Final thoughts

If you have been writing bash, you may have seen $@ and $* used in order to get all the arguments passed to the bash script. These, in fact, behave like arrays, which you are now well equipped to deal with! The * and @ stand for the same thing as they do for arrays.

Happy bashing!



Try Cloud 66 for Free, No credit card required