How to Use Arrays in Bash

How to Use Arrays in Bash

Arrays are one of those Bash features that a lot of people don’t touch for a long time, then wonder how they ever lived without once they get comfortable with them. Instead of juggling a bunch of separate variables like item1, item2, item3, arrays let you group related data together and loop through it cleanly. In this article, I’ll cover everything from declaring a simple array to working with associative arrays, which behave more like dictionaries or hash maps.

Declaring an Array

The simplest way to create an array in Bash is to assign values inside parentheses:

fruits=("apple" "banana" "cherry")

You can also declare an empty array first and add elements later:

declare -a fruits
fruits+=("apple")
fruits+=("banana")

Accessing Array Elements

To access a specific element, use its index (Bash arrays are zero-indexed):

echo "${fruits[0]}"   # Output: apple
echo "${fruits[1]}"   # Output: banana

To get all elements at once:

echo "${fruits[@]}"   # Output: apple banana cherry

Getting the Length of an Array

echo "${#fruits[@]}"   # Output: 3

This gives you the total number of elements in the array.

Looping Through an Array

for fruit in "${fruits[@]}"; do
    echo "$fruit"
done

Quoting "${fruits[@]}" is important here — without the quotes, elements containing spaces could get split incorrectly.

Adding and Removing Elements

Adding an element to the end of an array:

fruits+=("date")
echo "${fruits[@]}"   # Output: apple banana cherry date

Removing a specific element by index:

unset 'fruits[1]'
echo "${fruits[@]}"   # Output: apple cherry date

Note that unset leaves a gap in the index sequence rather than shifting everything down. If you need a clean, re-indexed array afterward, you can rebuild it:

fruits=("${fruits[@]}")

Slicing an Array

You can extract a portion of an array using a similar syntax to string slicing:

numbers=(10 20 30 40 50)
echo "${numbers[@]:1:3}"    # Output: 20 30 40

The first number is the starting index, and the second is how many elements to include.

Associative Arrays (Bash 4+)

Associative arrays let you use strings as keys instead of numeric indexes, similar to a dictionary in Python or an object in JavaScript.

declare -A capitals
capitals["France"]="Paris"
capitals["Japan"]="Tokyo"
capitals["Egypt"]="Cairo"

echo "${capitals["Japan"]}"    # Output: Tokyo

Looping Through an Associative Array

for country in "${!capitals[@]}"; do
    echo "$country -> ${capitals[$country]}"
done

Here, ${!capitals[@]} returns all the keys, and ${capitals[$country]} fetches the value for each key.

Checking If an Array Contains a Specific Value

There’s no dedicated “contains” function, but a simple loop or pattern match works well:

fruits=("apple" "banana" "cherry")
search="banana"

found=false
for fruit in "${fruits[@]}"; do
    if [ "$fruit" == "$search" ]; then
        found=true
        break
    fi
done

if $found; then
    echo "$search is in the array."
else
    echo "$search is not in the array."
fi

Converting a String to an Array

This is common when parsing delimited data:

csv="apple,banana,cherry"
IFS=',' read -ra fruit_array <<< "$csv"
echo "${fruit_array[1]}"   # Output: banana

Passing Arrays to Functions

print_array() {
    local arr=("$@")
    for item in "${arr[@]}"; do
        echo "$item"
    done
}

my_list=("one" "two" "three")
print_array "${my_list[@]}"

Since Bash doesn’t support passing arrays by reference in a straightforward way (prior to Bash 4.3’s nameref feature), the common pattern is to expand the array into positional arguments and rebuild it inside the function.

Using Namerefs to Pass Arrays by Reference (Bash 4.3+)

modify_array() {
    local -n arr_ref=$1
    arr_ref+=("added_item")
}

my_list=("one" "two")
modify_array my_list
echo "${my_list[@]}"   # Output: one two added_item

The -n flag creates a nameref, letting the function modify the caller’s array directly instead of working on a copy.

How Arrays Work Internally

Bash arrays are implemented as a sparse, ordered collection of key-value pairs, where the “key” is the numeric index for indexed arrays or a string for associative arrays. Internally, Bash stores each element along with its index in a linked structure, which is why gaps left by unset don’t cause errors — the array simply skips over missing indices when iterated with ${arr[@]}. This also explains why array length (${#arr[@]}) reflects the count of actual elements, not the highest index plus one. Associative arrays require Bash 4.0 or later because the underlying hash table implementation for string keys was introduced at that version; scripts relying on declare -A will fail on older Bash versions or on /bin/sh if it’s linked to a different shell like dash.

Real-World Use Cases

Storing a list of servers to loop through in a deployment script:

servers=("web01.example.com" "web02.example.com" "db01.example.com")

for server in "${servers[@]}"; do
    echo "Deploying to $server..."
    # ssh "$server" "deploy_command"
done

Mapping environment names to configuration files:

declare -A env_configs
env_configs["dev"]="config/dev.conf"
env_configs["staging"]="config/staging.conf"
env_configs["prod"]="config/prod.conf"

environment="staging"
echo "Using config: ${env_configs[$environment]}"

Collecting command output into an array for processing:

mapfile -t running_services < <(systemctl list-units --type=service --state=running --no-legend | awk '{print $1}')

for service in "${running_services[@]}"; do
    echo "Running: $service"
done

mapfile (also called readarray) reads lines of input directly into an array, which is often cleaner than a manual while read loop.

Automation Example: Batch File Processor

#!/bin/bash

declare -A file_status

for file in /data/incoming/*.csv; do
    if [ -s "$file" ]; then
        file_status["$file"]="valid"
    else
        file_status["$file"]="empty"
    fi
done

for file in "${!file_status[@]}"; do
    echo "$file: ${file_status[$file]}"
done

This script builds an associative array mapping each file to a status of “valid” or “empty,” which could then feed into further processing logic.

Best Practices

  • Always quote array expansions: "${array[@]}" rather than ${array[@]}, to prevent word-splitting on elements containing spaces.
  • Use declare -a for indexed arrays and declare -A for associative arrays to make your intent explicit, especially inside functions where you want the array scoped locally.
  • Prefer mapfile/readarray over manual while read loops when reading lines directly into an array.
  • Use namerefs (local -n) when a function genuinely needs to modify the caller’s array, rather than relying on global variables.

Security Considerations

  • Be careful when populating arrays from external input (like command output or user-supplied data) without validating it first, since maliciously crafted input could introduce unexpected elements or break your parsing logic.
  • When looping through arrays to build commands, prefer array-based argument passing (command "${args[@]}") over string concatenation, since it avoids unintended word-splitting or globbing of user-controlled values.

Optimization Tips

  • Associative arrays provide O(1) average lookup time for key-based access, which is significantly faster than looping through an indexed array to find a matching value when your dataset grows large.
  • Use mapfile instead of a while read loop for large files, since it tends to be faster for bulk reads by avoiding the overhead of a loop iteration per line.

Troubleshooting

Associative array declaration fails with “declare: -A: invalid option”: Your shell is likely not Bash, or you’re using an old Bash version below 4.0. Confirm with bash --version and make sure the script’s shebang is #!/bin/bash, not #!/bin/sh.

Looping through an array skips or misreads elements with spaces: This usually means you forgot to quote the array expansion. Use "${array[@]}" instead of ${array[@]}.

unset on an array element leaves a confusing gap: This is expected behavior. If you need a contiguous array afterward, reassign it: array=("${array[@]}").

Common Mistakes

  1. Forgetting to quote "${array[@]}", causing elements with spaces to split incorrectly.
  2. Trying to use associative arrays on a system where Bash defaults to a version older than 4.0.
  3. Assuming arrays can be passed to functions like normal variables, without expanding them properly.
  4. Confusing ${array[@]} (all elements) with ${array[*]} (all elements as a single string) — they behave differently when quoted.

FAQs

What’s the difference between ${array[@]} and ${array[*]}? When quoted, "${array[@]}" expands each element as a separate word, while "${array[*]}" expands all elements as a single string joined by the first character of IFS. For looping purposes, "${array[@]}" is almost always what you want.

Can Bash arrays hold different data types? Bash doesn’t have strict typing, so arrays can hold any string values, including ones that look like numbers, but everything is stored as text internally.

How do I check if an array is empty? Use if [ ${#array[@]} -eq 0 ]; then echo "Array is empty"; fi.

Are associative arrays ordered in Bash? No, associative arrays in Bash do not guarantee any particular iteration order, unlike indexed arrays which maintain their numeric order.

Summary

Arrays bring a level of structure to Bash scripting that plain variables can’t match, whether you’re working with a simple ordered list or a key-value associative array. The key habits to build are quoting your expansions properly, understanding the difference between [@] and [*], and reaching for associative arrays when you need lookups by name rather than position. Once these patterns click, arrays make scripts that deal with lists of files, servers, or configuration options far cleaner and less repetitive.

References

  • Bash Reference Manual (Arrays): https://www.gnu.org/software/bash/manual/bash.html#Arrays
  • GNU Bash Manual: https://www.gnu.org/software/bash/manual/bash.html
  • Bash Hackers Wiki on Arrays: https://web.archive.org/web/2023/https://wiki.bash-hackers.org/syntax/arrays
Total
2
Shares

Leave a Reply

Previous Post
How to Pass Arguments to a Bash Script

How to Pass Arguments to a Bash Script

Next Post
How to Use Strings in Bash

How to Use Strings in Bash

Related Posts