1. Introduction

Bash arrays are powerful data structures and can be useful when handling collections of files or strings.

In this tutorial, we’re going to explore how to use Bash arrays.

2. Types of Arrays

There are two types of arrays in Bash:

  1. indexed arrays: values are accessible through an integer index
  2. associative arrays (maps): values are accessible through a key

In our examples, we’ll mostly be using the first type, but occasionally, we’ll talk about maps as well.

One particular aspect is that Bash arrays don’t have a maximum limit. There’s also no requirement regarding continuous assignment.

We’ll understand the last part later. Now, let’s take a look at how to define arrays.

2.1. Indexed Arrays

We can declare indexed arrays in multiple ways.

Let’s use the declare keyword with the -aoption first:

$ declare -a indexed_array

Additionally, we can initialize the array with some string values:

$ declare -a indexed_array=("Baeldung" "is" "cool")

Since we provide some initial values, we can skip the declare keyword and flag:

$ indexed_array=("Baeldung" "is" "cool")

We can also use indexes in the initialization sequence:

$ indexed_array=([0]="Baeldung" [1]="is" [2]="cool")

Additionally, we can assign the elements one by one:

$ indexed_array[0]="Baeldung"
$ indexed_array[1]="is"
$ indexed_array[2]="cool"

In Bash, all these examples are valid definitions of arrays. In all cases, indexes start at zero.

How about the restrictions regarding contiguous assignments? Let’s see what happens:

$ indexed_array[0]="Baeldung"
$ indexed_array[2]="cool"

This example is also a valid definition of an array. Unlike the others, the element at index 1 is not assigned, but there’s no harm in that.

2.2. Associative Arrays

Now, let’s take a look at how to declare associative arrays.

Unlike indexed arrays, we must declare associative arrays explicitly using the -A option:

$ declare -A associative_array=(["one"]="Baeldung" ["two"]="is" ["three"]="cool")

Likewise, we can assign further values to an associative array by specifying a key and a corresponding value:

$ associative_array["four"]="yeah"

Of course, now we’ll have four elements in our map.

3. Basic Operations

Next, we take a look at some basic operations we can do with arrays.

3.1. Printing the Array Elements

Printing the array elements is one of the most intuitive and basic operations.

Let’s see what this looks like:

$ declare -a indexed_array=("Baeldung" "is" "cool")
$ echo "Array elements : ${indexed_array[@]}"
Array elements : Baeldung is cool

Here, we use the @ symbol as the index to specify all the members of an array. We also surround the array variable with the ${} construct triggering Bash parameter expansion.

Alternatively, we can also use the * symbol as an index to obtain the same output:

$ echo "Array elements : ${indexed_array[*]}"

However, there’s a difference between these two options.

3.2. Enhanced Iteration

Enhanced iteration is similar to list iterations in Java:

$ declare -a indexed_array=("Baeldung" "is" "cool")
$ for element in ${indexed_array[@]}
>   do
>     echo "["$element"]"
>   done

Here again, we use parameter expansion with ${indexed_array[@]} to return all the elements of the array.

Then, we just loop through it:

[Baeldung]
[is]
[cool]

Of course, this also works with the * symbol:

$ for element in ${indexed_array[*]}; do echo "["$element"]"; done

We can also use enhanced iteration on associative arrays:

$ declare -A associative_array=(["one"]="Baeldung" ["two"]="is" ["three"]="cool")
$ for element in ${associative_array[*]}; do echo "["$element"]"; done
[is]
[cool]
[Baeldung]

Consequently, the output isn’t ordered. The order in which the elements are printed isn’t the same as the order in the initialization sequence.

3.3. Iterating with Indexes

We can use index-based iteration with enhanced looping constructs on arrays:

$ declare -a indexed_array=("Baeldung" "is" "cool")
$ for index in ${!indexed_array[@]}; do echo "["$index"]:["${indexed_array[$index]}"]"; done
[0]:[Baeldung]
[1]:[is]
[2]:[cool]

Here, ${!indexed_array[@]} returns all the indexes of the array.

Let’s see what happens if we have an associative array instead:

$ declare -A associative_array=(["one"]="Baeldung" ["two"]="is" ["three"]="cool")
$ for key in ${!associative_array[@]}; do echo "["$key"]:["${associative_array[$key]}"]"; done
[two]:[is]
[three]:[cool]
[one]:[Baeldung]

The index values are now actually the keys in the map. Similarly, there’s no particular order in the output.

Of course, we can also do a classical incremental looping construct:

$ for ((index=0; index < ${#indexed_array[@]} ; index++)); do echo "["$index"]:["${indexed_array[$index]}"]"; done

Unlike the previous approach, we use ${#indexed_array[@]} to retrieve the number of elements in the array.

Earlier, we saw that associative arrays return keys rather than indexes. As a result, this type of incremental looping is unsuitable for maps.

3.4. Inserting Elements in Arrays

In the beginning, we said Bash arrays have no size and continuity constraints.

As a result, we can insert elements at any index using a simple assignment:

$ declare -a indexed_array=("Baeldung" "is" "cool")
$ indexed_array[2]="lorem"
$ indexed_array[5]="ipsum"
$ for index in ${!indexed_array[@]}; do echo "["$index"]:["${indexed_array[$index]}"]"; done

This isn’t a very intuitive way of adding values, and doing so yields confusing results:

[0]:[Baeldung]
[1]:[is]
[2]:[lorem]
[5]:[ipsum]

In this case, the indexes aren’t continuous. We just skipped index positions 3 and 4. This makes sense only when trying to replace an existing element in the array.

Assuming our previous example, we can use a cleaner approach to append values to our array:

$ indexed_array+=("lorem")
$ for index in ${!indexed_array[@]}; do echo "["$index"]:["${indexed_array[$index]}"]"; done
[0]:[Baeldung]
[1]:[is]
[2]:[cool]
[3]:[lorem]

In this example, we used the += construct to append a new value to our array. This can also be used to initialize the array.

3.5. Removing Elements from Arrays

When we want to remove items from an array, we use the unset construct:

$ declare -a indexed_array=("Baeldung" "is" "cool")
$ echo "Array elements : ${indexed_array[@]}"
Array elements : Baeldung is cool
$ unset indexed_array[1]
$ echo "Size of array after removal: ${#indexed_array[@]}"
Size of array after removal: 2
$ echo "Array elements after removal: ${indexed_array[@]}"
Array elements after removal : Baeldung cool

In this example, we remove the element at index 1, leaving the elements at index 0 and 2 unchanged.

Let’s see what happens if we use unset without providing any index:

$ declare -a indexed_array=("Baeldung" "is" "cool")
$ echo "Array elements : ${indexed_array[*]}"
Array elements : Baeldung is cool
$ unset indexed_array
$ echo "Size of array after removal: ${#indexed_array[@]}"
Size of array after removal: 0
$ echo "Removed complete array : ${indexed_array[@]}"
Removed complete array :

This means the array is completely removed. The same thing happens if we use the @ or * symbols as index:

$ declare -a indexed_array=("Baeldung" "is" "cool")
$ echo "Array elements : ${indexed_array[*]}"
Array elements : Baeldung is cool
$ unset indexed_array[@]
$ echo "Size of array:" ${#indexed_array[@]}
Size of array: 0
$ echo "Removed complete array : ${indexed_array[@]}"
Removed complete array :

Let’s now try to add an element in the array we just removed:

$ indexed_array+=("lorem ipsum")
$ echo "Array elements : ${indexed_array[@]}"
Array elements : lorem ipsum

This is possible because we’re re-initializing the array.

4. Advanced Operations

Now, let’s take a look at some advanced scenarios involving arrays.

4.1. Using Quotes in Iterations

Let’s see the difference between @ and *:

$ declare -a indexed_array=("Baeldung" "is" "cool")
$ for element in "${indexed_array[*]}"; do echo "["$element"]"; done
[Baeldung is cool]

This isn’t probably what we expected. Now, let’s try it again with *@*as the index:

$ for element in "${indexed_array[@]}"; do echo "["$element"]"; done
[Baeldung]
[is]
[cool]

Let’s understand what happened here. We used double quotes around the ${indexed_array[*]} construct.

As a result, when using the * symbol, the elements of the array are a single word separated by the character.

This approach is particularly useful when we have an array of elements that have characters inside:

$ declare -a indexed_array=("Baeldung is" "so much" "cool")
$ echo "Without quotes:"
Without quotes:
$ for element in ${indexed_array[@]}; do echo "["$element"]"; done
[Baeldung]
[is]
[so]
[much]
[cool]
$ echo "With quotes:"
With quotes:
$ for element in "${indexed_array[@]}"; do echo "["$element"]"; done
[Baeldung is]
[so much]
[cool]

Notably, the array elements are subject to further word splitting when using the unquoted construct.

4.2. Transforming Arrays

Normally, when we consider transforming elements of an array, the first thing that comes to mind is iteration.

Luckily, Bash provides some neat tricks with parameter expansions that spare us from iterating. We’ll only take a look at a few interesting ones, but we can always check the Bash man page for more.

Let’s first search and replace a specific element in our array:

$ declare -a indexed_array=("Baeldung is" "so much" "cool" "cool cool")
$ echo "Initial array : ${indexed_array[@]}"
Initial array : Baeldung is so much cool cool cool
$ echo "Replacing cool with better: ${indexed_array[@]/cool/better}"
Replacing cool with better: Baeldung is so much better better cool

Here we used the ${indexed_array[@]/cool/better} syntax to achieve this replacement.

However, this only replaces the first occurrence of the string in each element.

We can replace all the occurrences of a string using the <*//*> syntax:

$ declare -a indexed_array=("Baeldung is" "so much" "cool" "cool cool")
$ echo "Replacing cool with better: ${indexed_array[@]//cool/better}"
Replacing cool with better: Baeldung is so much better better better

Let’s see what happens if we don’t specify a replacement string:

$ echo "Replacing cool with nothing: ${indexed_array[@]//cool}"
Replacing cool with nothing: Baeldung is so much

This removes the matched string from our array. We need to keep in mind that this kind of search is case-sensitive by default unless we modify the optional behavior of our shell.

Now, let’s play around with changing to upper-case:

$ declare -a indexed_array=("Baeldung" "is" "cool")
$ echo "Uppercasing sentence case: ${indexed_array[@]^}"
Uppercasing sentence case: Baeldung Is Cool
$ echo "Uppercasing all characters: ${indexed_array[@]^^}"
Uppercasing all characters: BAELDUNG IS COOL

Here, we use the ^ and ^^ to change to upper-case.

On the other hand, we can also perform lower-case transformations with , and ,, constructs:

$ indexed_array=("BAeldung" "Is" "COol")
$ echo "Lowercasing sentence case: ${indexed_array[@],}"
Lowercasing sentence case: bAeldung is cOol
$ echo "Lowercasing all characters: ${indexed_array[@],,}"
Lowercasing all characters: baeldung is cool

Thus, we can perform lower-case to upper-case transformations and vice-versa with the Bash arrays.

4.3. Assignment Between Arrays

In the previous transformation examples, the initial array wasn’t modified.

If we want to keep the results, we can assign them in a separate array:

$ indexed_array=("BAeldung" "Is" "COol")
$ lowercased_array=(${indexed_array[@],})
$ echo "Lowercasing sentence case: ${lowercased_array[@]}"
Lowercasing sentence case: bAeldung is cOol

Let’s take another example of transformation where this assignment is useful:

$ indexed_array=("Baeldung is" "so much" "cool")
$ echo "Uppercasing sentence case1: ${indexed_array[@]^}"
Uppercasing sentence case1: Baeldung is So much Cool
$ echo "No of elements in first_array: ${#indexed_array[@]}"
No of elements in first_array: 3
$ second_array=(${indexed_array[@]})
$ echo "Uppercasing sentence case2: ${second_array[@]^}"
Uppercasing sentence case2: Baeldung Is So Much Cool
$ echo "No of elements in second_array: ${#second_array[@]}"
No of elements in second_array: 5

We would probably expect the two outputs to match. However, this isn’t the case.

So, what happened? Let’s recall what quoting did in the previous iteration examples.

Since we didn’t use quotes, Bash applied word splitting, and the second_array contains more elements than the first.

In some situations, assignment can be useful for merging arrays:

$ declare -a fist_array=("Baeldung" "is" "cool")
$ declare -a second_array=("lorem" "ipsum")
$ declare -a merged=(${fist_array[@]} ${second_array[@]})
$ echo "First array : ${fist_array[@]}"
First array : Baeldung is cool
$ echo "Second array : ${second_array[@]}"
Second array : lorem ipsum
$ echo "Merged array : ${merged[@]}"
Merged array : Baeldung is cool lorem ipsum

Let’s take a closer look at what this sequence does. First, we declare first_array and second_array separately. Then, we initialize the merged array with all the elements of first_array and second_array.

4.4. Offset and Length Traversal

In Bash, this is formally known as substring expansion but works as well on indexed arrays. However, for maps, it has undefined behavior.

Sometimes, we need to extract specific parts of an array:

$ declare -a indexed_array=("Baeldung" "is" "cool" "and" "better" "than" "before")
$ echo "Offset 1 length 3: ${indexed_array[@]:1:3}"
Offset 1 length 3: is cool and

This construct takes an input offset and a length. It then selects length x elements starting at index = offset.

What if we omit the length:

$ echo "Offset 1 no length: ${indexed_array[@]:1}"
Offset 1 no length: is cool and better than before

We then get all the elements of the array starting at the offset until the end.

Now, let’s see whether using a negative offset changes anything:

$ echo "Offset -1 length 3: ${indexed_array[@]: -4:3}"
Offset -1 length 3: and better than

The negative offset is considered relative to the maximum index of the array.

Bash confuses it with another construct if we don’t use <*whitespace*> character while using negative offsets.

5. Conclusion

In this article, we looked at how to work with Bash arrays. We saw there is support for both associative and index-based arrays.

After that, we looked at basic iteration strategies and how to insert and remove elements. Then, we played around with transformations and assignments between arrays. Finally, we showed how to extract specific parts of arrays using offset and length traversal.