Arrays (Bash)

Uit De Vliegende Brigade
Naar navigatie springen Naar zoeken springen

In Bash, an array is a one-dimensional sequence of variables with an index.

An array has only one dimension

In Bash, there are no multi-dimensional arrays. You have associative arrays which may trick you in believing they are multi-dimensional, but they really aren't.

So, an array is pretty much like a vector: A sequence of entities.

This article is about these 'normal', 'straight' arrays. Not about associative arrays.

Base zero

With normal arrays, I don't think you get a say about how the index looks like. It's just an integer sequence, and it starts at 0: Bash arrays are base zero.

j=(1 2 3 blub Blub enough)
echo "Array j: ${j[@]}"
echo "Index j: ${!j[@]}"

Output:

Array j: 1 2 3 blub Blub enough
Index j: 0 1 2 3 4 5

No declaration

An array doesn't need to be declared before being used:

j=(1 2 3 blub Blub enough)
echo "Array j: ${j[@]}"

Output:

Array j: 1 2 3 blub Blub enough

Note that the entities are printed on one line - That might help you to detect variables that may look like arrays, but are just weird multi-line variables, or whatever.

It's called an entry

The thingy that an array is composed of, seems to be called entry. I'm surprised how many synonyms I know for this word, but when confused, I might just call it an entry.

Assign individual entries

unset j
j[0]="Foo"
j[1]="bar"

echo ${j[0]}    # Foo
echo ${j[1]}    # bar
echo ${j[@]}    # Display all entries: Foo bar
echo ${#j[@]}   # Number of entries: 2
echo ${!j[@]}   # Display ll indices: 0 1

Assign an array using parenthesis

OK, it has been demonstrated a couple of time by now, but just for the record:

j=(this is my shiny new array and it has 9 entries)

echo "Size: ${#j[@]}"
echo "Content: ${j[@]}"

echo "Index 	Value"
for i in ${!j[@]}
do
   echo "$i:	${j[$i]}"
done

Output:

Size: 11
Content: this is my shiny new array and it has 9 entries
Index 	Value
0:	this
1:	is
2:	my
3:	shiny
4:	new
5:	array
6:	and
7:	it
8:	has
9:	9
10:	entries

Place entries that contain spaces, within double quotes:

j=("This is the first entry" "This is the second entry")
echo ${#j[@]}

Output:

2

Index & syntax

Just some starting stuff:

a=(1 2 "Drie" 4 "Vijf")

echo $a        # Returns "1" - First item of the array
echo $a[0]     # Returns "1[0]" - The [0] part is treated as a literal
echo ${a[0]}   # Returns "1"
echo ${a[@]}   # Returns "1 2 3 4" - whole array
a[0]="Nul"
a[1]="Eén"

echo $a        # Returns first element
echo $a[0]     # Output: Null[0] - Probably not what you wanted?
echo ${a[1]}   # OK

Associative arrays

An associative array is an array where the index doesn't have to be a number. I really like them, and I abuse them a lot to make you think they're actually multi-dimensional arrays. See Associative arrays (Bash) for more.

Length of an array

echo "Length of array j0: ${#j0[@]}"

Return all indices

j=(I never get enough of creating nice arrays. And you?)
echo ${!j[@]}

Output:

0 1 2 3 4 5 6 7 8 9

This might come handy for loops:

j=(I never get enough of creating nice arrays. And you?)
echo ${!j[@]}

echo "Index 	Value"
for i in ${!j[@]}
do
   echo "$i:	${j[$i]}"
done

Output:


0 1 2 3 4 5 6 7 8 9
Index 	Value
0:	I
1:	never
2:	get
3:	enough
4:	of
5:	creating
6:	nice
7:	arrays.
8:	And
9:	you?

Loop through array entries

Dit werkt. Merk op dat $i de waarde van de cel bevat, niet de index:

array=( one two three )
for i in "${array[@]}"
do
   echo "$i"
done

Loop through array index

Rather than going through the entries of an array, let's loop through its index. Note that this is base 0:

array=( one two three )
for i in "${!array[@]}"
do
   echo "Index: $i - Value: ${array[i]}"
done

Output:

Index: 0 - Value: one
Index: 1 - Value: two
Index: 2 - Value: three

Loop through array index - This doesn't work

array_rows=12

for i in $array_rows
do
   echo $i
done

The only output will be 12 - There won't be any loop. So, just giving a number as argument for a loop, doesn't work.

Unset

Use unset to destroy an array. But do you really need it?

Script:

j=(1 2 3 enough)
echo "Size j: ${#j[@]}"

j=(5 6)
echo "Size j: ${#j[@]}"

unset j
echo "Size j: ${#j[@]}"

Output:

Size j: 4
Size j: 2
Size j: 0
  • When you assign again a value to an array, the old values get overwritten - No need to use unset
  • After unset, its length is 0
  • Might be a good habit to unset an array after use, to free up the memory.

Reading output of a command into an array

I want to use WP-CLI command wp post list to return IDs of products. I want to store these IDs in an array. Subsequently, I want to use parallelisation to do some nifty stuff with these IDs, without having to wait until tomorrow morning for the results.

This doesn't work:

j=$(wp post list --post_type=product --field=ID --posts_per_page=64)

Eventhough echo ${j[@]} gives a promising result, I think it's just one string and not an array.

This works [1]:

unset j
mapfile -t j < <( wp post list --post_type=product --field=ID --posts_per_page=64 | grep . )

echo "Whole array: ${j[@]}"
echo "Item 5: ${j[5]}"

With grep . to trim some leading whitespace.

BTW: mapfile really creates an array, and not some kind of multi-line variable or a variable with a lot of spaces & numbers.

Arithmatic with arrays

This works, so no (()) black magic today. At least, not here:

i=5
echo "Entry i (=5): ${j[$i]}"
echo "Entry i+1: ${j[$i+1]}"
echo "Entry 6: ${j[6]}"

Merge arrays

i1=(1 2 drie vier)
i2=(4 5 blub Blub)
echo "Array i1: ${i1[@]}"
echo "Array i2: ${i2[@]}"

i3=( "${i1[@]}" "${i2[@]}" )
echo "Array i3: ${i3[@]}"

Output:

Array i1: 1 2 drie vier
Array i2: 4 5 blub Blub
Array i3: 1 2 drie vier 4 5 blub Blub

Append

Here are about all ways to append values, that I could reasonably come up with. It reminds me that there is more than one way to skin a cat. Especially in Bash:

Single item using '+=' shorthand operator

j=(1 2 3 4 enough)
j+=("One more")
echo ${j[@]}

Output:

1 2 3 4 enough One more

Another array using '+=' shorthand operator

j=(1 2 3 4 enough)
k=(3 4 vijf)

j+=("${k[@]}")
echo ${j[@]}

Output:

1 2 3 4 enough 3 4 vijf

Single item by index

With ${#j[@]}, we retrieve the number of entries. This would also be the index for a new entry, as Bash arrays are base zero:

j=(1 2 3 4 enough)
echo ${#j[@]}
j[${#j[@]}]="Oops, just one more"
echo ${#j[@]}
echo ${j[@]}

Output:

5
6
1 2 3 4 enough Oops, just one more

Single item using parenthesis

j=(1 2 3 4 enough)
j=(${j[@]} "No! Just one more, please!")

echo ${j[@]}
echo ${#j[@]}

Output:

1 2 3 4 enough No! Just one more, please!
6

Multiple item using parenthesis

The approach above, can easily be extended to add multiple items:

j=(1 2 3 4 enough)
j=(${j[@]} 6 7 8 9 10)

But this doesn't work:

j=(1 2 3 4 enough)
j=(${j[@]} (6 7 8 9 10))

Another array using parenthesis

This is actually why I wrote this section on appending:

j=(1 2 3 4 enough)
k=(blub blub 5 6 7 8 9 10)
j=(${j[@]} ${k[@]})

echo ${j[@]}
echo ${#j[@]}

Output:

1 2 3 4 enough blub blub 5 6 7 8 9 10
13

Remove an entry

$ j=(one two three)
$ echo ${#j[@])
3

$ echo ${!j[@]}
0 1 2


$ unset 'j[1]'
$ echo ${j[@]}
one three

$ echo ${#j[@]}
2

# Note that the index is not rebuild - An item is missing:
$ echo ${!j[@]}
0 2

Case: Something doesn't add up (2022.10)

The output of a WP-CLI command is read into an array. But how long is the array? There seem to be 64, 65 and/or 66 items, depending on how you ask:

unset j
mapfile -t j < <( wp post list --post_type=product --field=ID --posts_per_page=64 )

echo "Whole array: ${j[@]}"        # 64 IDs
echo "Item 5: ${j[5]}"
echo "All indices: ${!j[@]}"       # 0 to 65 = 66 indices
echo "Number of items: ${#j[@]}"   # 66

Answer: The first two entries contain NULL: WP-CLI creates quite some whitespace. Use grep . to filter out this whitespace:

unset j
mapfile -t j < <( wp post list --post_type=product --field=ID --posts_per_page=64 | grep . )

echo "Whole array: ${j[@]}"
echo "Item 5: ${j[5]}"
echo "All indices: ${!j[@]}"
echo "Number of items: ${#j[@]}"

See also

Sources