Bash Array

Bash Array Explained: A Complete Guide

The Bash array data type gives you a lot of flexibility in the creation of your shell scripts.

Bash provides two types of arrays: indexed arrays and associative arrays. Indexed arrays are standard arrays in which every element is identified by a numeric index. In associative arrays every element is a key-value pair (similarly to dictionaries in other programming languages).

In this tutorial we will start by getting familiar with indexed arrays, then we will also see how associative arrays differ from them (they also have few things in common).

By the end of this tutorial you will clarify any doubts you might have right now about arrays in Bash.

And you will also learn lots of cool things you can do in your shell scripts with arrays.

Let’s get started!

Bash Indexed Array of Strings

We will start by creating an indexed array of strings where the strings are directory names in a Linux system:

dirs=("/etc" "/var" "/opt" "/tmp")

First of all let’s see what gets printed when we echo the value of the array variable dirs:

$ echo $dirs
/etc 

When you print a Bash array variable the result is the first element of the array.

Another way to print the first element of the array is by accessing the array based on its index.

Bash indexed arrays are zero based, this means that to access the first element we have to use the index zero.

$ echo ${dirs[0]}
/etc

Wonder why we are using curly brackets?

We can understand why by removing them to see what the ouput is:

$ echo $dirs[0]
/etc[0]

Bash prints the first element of the array followed by [0] because it only recognises $dirs as a variable. To include [0] as part of the variable name we have to use curly brackets.

In the same way, to print the second element of the array we will access the index 1 of the array:

$ echo ${dirs[1]}
/var

What if we want to access the last element of the array?

Before doing that we have to find out how to get the length of a Bash array…

How Do You Determine the Length of a Bash Array?

To find the length of an array in Bash we have to use the syntax ${#array_name[@]}.

Let’s apply it to our example:

$ echo ${#dirs[@]}
4

The syntax might seem hard to remember when you see it for the first time…

…but don’t worry, just practice it a few times and you will remember it.

Access the Last Element of a Bash Array

Now that we know how to get the number of elements in a Bash array, we can use this information to retrieve the value of the last element.

First we have to calculate the index of the last element that is equal to the number of elements in the array minus one (remember that Bash arrays are zero-based as usually happens in most programming languages).

${#dirs[@]}-1

This value will be the index to pass when we want to print the last element of the array:

$ echo ${dirs[${#dirs[@]}-1]}
/tmp

Definitely not one of the easiest ways to retrieve the last element of an array, if you are familiar with other programming languages 😀

Since Bash 4.2 arrays also accept negative indexes that allow to access elements starting from the end of the array.

To verify your version of Bash use the following command:

$ bash --version

To access the last element of a Bash indexed array you can use the index -1 (for Bash 4.2 or later). Otherwise use the following expression ${array_name[${#array_name[@]}-1]}.

$ dirs=("/etc" "/var" "/opt" "/tmp")
$ echo ${dirs[-1]}
/tmp

As expected we get back the last element.

How to Print All the Values in a Bash Array

To print all the elements of an array we still need to use the square brackets and replace the index with the @ symbol:

$ echo ${dirs[@]}
/etc /var /opt /tmp

An alternative to the @ is the * sign:

$ echo ${dirs[*]}
/etc /var /opt /tmp

Why two ways to do the same thing?

What’s the difference between * and @ when used to print all the elements of a Bash array?

We will see it later, after showing you how to use a for loop to go through all the elements of an array…

How to Update a Bash Array Element

Now, how can we update an element in our array?

We will use the following syntax:

array_name[index]=new_value

In our case I want to set the value of the second element (index equal to 1) to “/usr”.

$ dirs[1]="/usr"
$ echo ${dirs[@]}
/etc /usr /opt /tmp

Loop Through Bash Array Elements

Let’s find out how to create a for loop that goes through all the elements of an array:

for dir in ${dirs[@]}; do
    echo "Directory name: $dir"
done

The output is:

Directory name: /etc
Directory name: /var
Directory name: /opt
Directory name: /tmp

Going back to the difference between * and @, what happens if we replace ${dirs[@]} with ${dirs[*]}?

for dir in ${dirs[*]}; do
    echo "Directory name: $dir"
done

No difference…

Directory name: /etc
Directory name: /var
Directory name: /opt
Directory name: /tmp

The difference becomes evident when we surround the two expressions with double quotes.

Using @

for dir in "${dirs[@]}"; do
    echo "Directory name: $dir"
done

[output]
Directory name: /etc
Directory name: /var
Directory name: /opt
Directory name: /tmp

Using *

for dir in "${dirs[*]}"; do
    echo "Directory name: $dir"
done

[output]
Directory name: /etc /var /opt /tmp

You can see that when we use the * our array is interpreted as a single value.

For Loop Using the Indexes of a Bash Array

Let’s try something else…

We will use the following expression:

${!array_name[@]}

Notice that we have added an exclamation mark before the name of the array.

Let’s see what happens when we do that.

$ echo ${!dirs[@]}
0 1 2 3

This time instead of printing all the elements of the array we have printed all the indexes.

The expression ${!array_name[@]} is used to print all the indexes of a Bash array.

As you can imagine we can use this to create a for loop that instead of going through all the elements of the array goes through all the indexes of the array. From 0 to the length of the array minus 1:

for index in ${!dirs[@]}; do
    echo "Directory name: ${dirs[$index]}"
done

Verify that the output is identical to the one we have seen by going through all the elements in the array instead of all the indexes.

We can also print the index for each element if we need it:

for index in ${!dirs[@]}; do
    echo "Index: $index - Directory name: ${dirs[$index]}"
done

Using Declare For Indexed Arrays

We have created our indexed array in the following way:

dirs=("/etc" "/var" "/opt" "/tmp")

Below you can see other two ways to create indexed arrays:

Option 1

Define an empty array and set its elements one by one:

dirs=()
dirs[0]="/etc"
dirs[1]="/var"
dirs[2]="/opt"
dirs[3]="/tmp"
echo ${dirs[@]}

[output]
/etc /var /opt /tmp

Option 2

Using the Bash declare builtin with the -a flag:

declare -a dirs

Append Elements to a Bash Indexed Array

To append an element to an existing array we can use the following syntax:

existingArray+=("newValue")

For example:

$ dirs=("/etc" "/var" "/opt" "/tmp")
$ dirs+=("/bin")
$ echo ${dirs[@]}
/etc /var /opt /tmp /bin

What about appending more than one element?

Here’s how you can do it…

$ dirs+=("/bin" "/usr")
$ echo ${dirs[@]}
/etc /var /opt /tmp /bin /usr

Makes sense?

How to Remove an Element From an Array

To remove an element from an array you can use unset:

$ dirs=("/etc" "/var" "/opt" "/tmp")
$ unset dirs[2]
$ echo ${dirs[@]}
/etc /var /tmp

Notice how the third element of the array (identified by the index 2) has been removed from the array.

You can also use unset to delete the entire array:

$ unset dirs
$ echo ${dirs[@]}

Confirm that the last echo command doesn’t return any output.

Summary of Bash Array Operations

Before moving to associative arrays I want to give you a summary of the Bash array operations we have covered.

SyntaxDescription
array=()Create an empty array
declare -a arrayCreate an empty indexed array with declare
array=(1 2 3 4 5)Initialise an array with five elements
${array[0]}Access first element of the array
${array[1]}Access second element of the array
${dirs[${#dirs[@]}-1]}Access last element of the array
${array[@]}Get all the elements of the array
${!array[@]}Get all the indexes of the array
array+=(6 7)Append two values to the array
array[2]=10Assign value to the third element of the array
${#array[@]}Get size of the array
${#array[n]}Get the length of the nth element

Practice all the commands in this table before continuing with this tutorial.

Many of the operations in the table also apply to associative arrays.

Initialize a Bash Associative Array

Associative arrays can be only defined using the declare command.

As we have seen before, to create an indexed array you can also use the following syntax:

declare -a new_array

To create an associative array change the flag passed to the declare command, use the -A flag:

$ declare -A new_array
$ new_array=([key1]=value1 [key2]=value2)
$ echo ${new_array[@]}
value2 value1

Notice how the order of the elements is not respected with Bash associative arrays as opposed as with indexed arrays.

When you have an array with lots of elements it can also help writing the commands that assign key/value pairs to the array in the following way:

new_array=(
    [key1]=value1
    [key2]=value2
)

How to Use a For Loop with a Bash Associative Array

The syntax of the for loops for associative arrays is pretty much identical to what we have seen with indexed arrays.

We will use the exclamation mark to get the keys of the array and then print each value mapped to a key:

for key in ${!new_array[@]}; do
    echo "Key: $key - Value: ${new_array[$key]}"
done

The output is:

Key: key2 - Value: value2
Key: key1 - Value: value1

Can you see how every key is used to retrieve the associated value?

Delete an Element From an Associative Array

Let’s see how you can delete an element from an associative array…

The following command removes the element identified by the key key1 from the associative array we have defined previously.

$ unset new_array[key1]

Confirm that you get the following output when you execute the for loop we have seen in the previous section:

Key: key2 - Value: value2

To delete the full array you can use the same syntax we have seen with indexed arrays:

unset new_array

The next few sections will show you some useful operations you can perform with Bash arrays as part of your day-to-day scripting…

Remove Duplicates From an Array

Have you ever wondered how to remove duplicates from an array?

To do that we could use a for loop that builds a new array that only contains unique values.

But instead, I want to find a more concise solution.

We will use four Linux commands following the steps below:

  1. Print all the elements of the array using echo.
  2. Use tr to replace spaces with newlines. This prints all the element on individual lines.
  3. Send the output of the previous step to the sort and uniq commands using pipes.
  4. Build a new array from the output of the command created so far using command substitution.

This is the original array and the output described up to step 3:

$ numbers=(1 2 3 2 4 6 5 6)
$ echo ${numbers[@]} | tr ' ' '\n' | sort | uniq
1
2
3
4
5
6

Now, let’s use command substitution, as explained in step 4, to assign this output to a new array. We will call the new array unique_numbers:

$ unique_numbers=($(echo ${numbers[@]} | tr ' ' '\n' | sort | uniq))

The following for loops prints all the elements of the new array:

for number in ${unique_numbers[@]}; do
    echo $number
done

The output is correct!

1
2
3
4
5
6

I wonder if it also works for an array of strings…

$ words=("bash" "array" "bash" "command" "bash" "shell" "associative")
$ unique_words=($(echo ${words[@]} | tr ' ' '\n' | sort | uniq))
$ for word in ${unique_words[@]}; do echo $word; done

Notice how we have written the Bash for loop in a single line.

Here is the output. It works for an array of strings too…

array
associative
bash
command
shell

With this example we have also seen how to sort an array.

Check If a Bash Array Contains a String

To verify if an array contains a specific string we can use echo and tr in the same way we have done in the previous section.

Then we send the output to the grep command to confirm if any of the elements in the array matches the string we are looking for.

Here is how it works if, for example, we look for the string “command”:

$ words=("array" "associative" "bash" "command" "shell")
$ echo ${words[@]} | tr ' ' '\n' | grep "command"
command

We can use the -q flag for grep to avoid printing any output. The only thing we need is the exit code of the command stored in the $? variable.

We can then use an if else statement to verify the value of $?

echo ${words[@]} | tr ' ' '\n' | grep -q "command"

if [ $? -eq 0 ]; then
    echo "String found in the array."
else
    echo "String not found in the array."
fi

This is how we verify if the array has an element equal to “command”.

In the same way we can find if a Bash associative array has a key.

We would simply replace ${words[@]} with ${!words[@]} to print all the keys instead of the values.

Give it a try!

Bash Array of Files in a Directory

I want to show you another example on how to generate an array from a command output.

This is something you will definitely find useful when creating your scripts.

We will create an array from the output of the ls command executed in the current directory:

$ files=($(ls -A))
$ echo ${files[@]}
.hidden_file1 test_file1 test_file2

Once again, notice how we use command substitution to assign the output of the command to the elements of the array.

How to Reverse an Array in Bash

We can use a very similar command to the one used to remove duplicates from an array also to reverse an array.

The only difference is that we would also use the Linux tac command (opposite as cat) to reverse the lines we obtain from the elements of the array:

$ words=("array" "associative" "bash" "command" "shell")
$ reversed_words=($(echo ${words[@]} | tr ' ' '\n' | tac))
$ echo ${reversed_words[@]}
shell command bash associative array

Makes sense?

How to Copy a Bash Indexed Array

Here is how you can copy an indexed array in Bash.

Given the following array:

words=("array" "bash" "command line" "shell")

I can create a copy using the following command:

array_copy=("${words[@]}") 

With a for loop we can confirm the elements inside the copy of the array:

for element in "${array_copy[@]}"; do
    echo $element
done

[output]
array
bash
command line
shell

Slicing a Bash Array

Sometimes you might want to just get a slice of an array.

A slice is basically a certain number of elements starting at a specific index.

This is the generic syntax you would use:

${array[@]:index:number_of_elements}

Let’s test this expression on the following array:

words=("array" "bash" "command line" "shell")

Two elements starting from index 1

$ echo ${words[@]:1:2}
bash command line 

One element starting from index 0

$ echo ${words[@]:0:1}
array 

Three elements starting from index 0

$ echo ${words[@]::3}
array bash command line 

To get all the elements of an array starting from a specific index (in this case index 1) you can use the following:

$ echo ${words[@]:1}
bash command line shell 

Search And Replace An Array Element

At some point you might need to search a replace an element with a specific value…

…here’s how you can do it:

echo ${array[@]/value_to_search/replace_with_this_value}

In our array I want to replace the word bash with the word linux:

$ words=("array" "bash" "command line" "shell")
$ echo ${words[@]/bash/linux}
array linux command line shell 

Quite handy!

I wonder if it works if there are multiple occurrences of the element we want to replace…

$ words=("array" "bash" "command line" "shell" "bash")
$ echo ${words[@]/bash/linux}
array linux command line shell linux 

It works!

How to Concatenate Two Bash Arrays

I want to concatenate the following two arrays:

commands1=("cd" "cat" "echo" "grep")
commands2=("sort" "rm" "top" "awk")

I can create a new array as a result of a merge of the two arrays:

all_commands=("${commands1[@]}" "${commands2[@]}")

Let’s confirm the values and the number of elements in this array:

$ echo ${all_commands[@]}
cd cat echo grep sort rm top awk
$ echo ${#all_commands[@]}
8 

Great!

Verify If Bash Array Is Empty

Why would you check if a Bash array is empty?

There are multiple scenarios in which this could be useful, one example is if you use an array to store all the errors detected in your script.

At the end of your script you check the number of elements in this array and print an error message or not depending on that.

We will use an array called errors and a Bash if else statement that checks the number of elements in the array.

In this example I will create the errors array with one element:

errors=("File not found")
 
if [ ${#errors[@]} -eq 0 ]; then
    echo "No errors found."
else
    echo "WARNING - Number of errors found: ${#errors[@]}"
fi

When I run the script I get the following output:

WARNING - Number of errors found: 1 

A nice way to track errors in your scripts!

Create a Bash Array From a Range of Numbers

How can I create an array whose elements are numbers between 1 and 100?

We will do it in the following way:

  • Create an empty array.
  • Use a for loop to append the numbers between 1 and 100 to the array.
numbers=() 

for value in {1..100}; do
    numbers+=($value)
done 

echo ${numbers[@]} 

Give it a try and confirm that the numbers between 1 and 100 are printed by the script.

We can also verify the number of elements in the array:

$ echo ${#numbers[@]}
100

How To Implement a Push Pop Logic for Arrays

Given an indexed array of strings:

words=("array" "bash" "command line" "shell") 

I want to implement a push pop logic…

…where push adds an element to the end of the array and pop removes the last element from the array.

Let’s start with push, we will just have to append an element the way we have seen before:

$ words+=("filesystem")
$ echo ${words[@]}
array bash command line shell filesystem 

The pop logic gets the value of the last element and then removes it from the array:

$ last_element=${words[-1]}
$ echo $last_element 
filesystem
$ unset words[-1]
$ echo ${words[@]}
array bash command line shell 

You can also wrap those commands in two Bash functions so you can simply call push() and pop() instead of having to duplicate the code above every time you need it.

Bad Array Subscript Error

At some point while I was working on this tutorial I encountered the following error:

./arrays.sh: line 4: dirs: bad array subscript

I was executing the following script:

#!/bin/bash

dirs=("/etc" "/var" "/opt" "/tmp")
echo dirs ${dirs[-1]}

Apparently there wasn’t anything wrong in line 4 of the script.

As we have seen in one of the previous sections, the index -1 can be used to access the last element of an array.

After a bit of troubleshooting I realised that the problem was caused by…

…the version of Bash running on my machine!

$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
Copyright (C) 2007 Free Software Foundation, Inc.

In version 3 Bash didn’t support negative indexes for arrays and, as explained in the section of this article “Access the Last Element of a Bash Array”, alternative solutions are possible.

Another option is upgrading your version of Bash as long as it’s supported by your operating system.

Let’s see another scenario in which this error can occur…

Here’s another script:

#!/bin/bash

declare -A my_array=([]="a" [key2]="b")

As you can see I’m using the declare builtin to create an associative array (I’m using the -A flag as learned in one of the sections above).

When I run the script I see the following error:

./array_error.sh: line 3: []="a": bad array subscript

This time, as you can see from the message, the error is caused by the fact that I’m trying to add an element with an empty key to the array.

This is another reason why this error can occur.

So, now you know two different causes of the “bad array subscript” error and if you see it in your scripts you have a way to understand it.

Conclusion

We have covered so much in this blog post!

You should be a lot more comfortable with Bash arrays now compared to when you started reading this article.

Let’s do a quick recap of the topics we covered, you might spot something you want to go back and review.

We have seen how to:

  • Define indexed and associative arrays.
  • Determine the length of an array.
  • Access elements based on indexes (for indexed arrays) and keys (for associative arrays).
  • Print all the elements using either @ or *.
  • Update array elements.
  • Loop through a Bash array using either the elements or the indexes.
  • Create indexed and associative arrays using the declare builtin.
  • Append elements to an existing array.
  • Remove elements from an array or delete the entire array.
  • Remove duplicates from an array.
  • Check if an array contains an element that matches a specific string.
  • Reverse, copy and get a slice of an array.
  • Search and replace a string in arrays.
  • Concatenate two arrays and verify if an array is empty.
  • Create an array from a range of numbers.
  • Implement a push / pop logic for Bash arrays.
  • Understand the “bad array subscript” error.

Now it’s your time to use Bash arrays….

…but before finishing I have a question for you to test your knowledge.

Given the following array:

declare -A my_array=([key1]="value1" [key2]="value2" [key3]="value3")
  1. What type of array is this one? Indexed or associative?
  2. How can you print the keys of this array?

Let me know in the comments.

Happy scripting! 🙂

Share knowledge with your friends!

Leave a Reply

Your email address will not be published. Required fields are marked *