Bash Arrays and Associative Arrays

September 18, 2026

Bash arrays are one of those features that many people overlook in favor of piping commands together. Once you start using them, you will find yourself reaching for them constantly. Let's walk through both indexed arrays and associative arrays with practical examples.

Indexed Arrays

An indexed array is a numbered list of values. You can create one in several ways.

# Method 1: declare and assign
fruits=("apple" "banana" "cherry" "date")

# Method 2: assign by index
colors[0]="red"
colors[1]="green"
colors[2]="blue"

# Method 3: from command output
log_files=($(find /var/log -name "*.log" -maxdepth 1))

Accessing Elements

Array indexing starts at zero. Use ${array[index]} to read a value.

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

echo "${fruits[0]}"    # apple
echo "${fruits[2]}"    # cherry
echo "${fruits[-1]}"   # date (last element)

All Elements and Length

Two special expansions give you every element and the total count.

echo "${fruits[@]}"    # apple banana cherry date
echo "${#fruits[@]}"   # 4

The [@] expansion is important. Always use it inside double quotes when iterating, so elements with spaces are preserved.

files=("my report.txt" "data file.csv" "notes.md")

# Wrong - splits on spaces
for f in ${files[@]}; do echo "$f"; done

# Correct - preserves spaces
for f in "${files[@]}"; do echo "$f"; done

Adding and Removing Elements

# Append to the end
fruits+=("elderberry")
fruits+=("fig" "grape")

# Remove by index (leaves a gap)
unset 'fruits[1]'

# Show indices to see the gap
echo "${!fruits[@]}"   # 0 2 3 4 5 6

# Remove the entire array
unset fruits

Note that unset on an index does not re-number the remaining elements. If you need a contiguous array after deletion, reassign it.

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

Slicing

You can extract a subset of an array with ${array[@]:offset:length}.

letters=("a" "b" "c" "d" "e" "f")

echo "${letters[@]:2:3}"   # c d e
echo "${letters[@]:4}"     # e f (from index 4 to the end)

Practical Example: Batch Processing Files

Here is a script that collects files into an array, processes them in batches, and reports progress.

#!/bin/bash
batch_size=5
files=($(find /var/log -name "*.log" -maxdepth 1 2>/dev/null))
total=${#files[@]}

echo "Found $total log files"

for ((i = 0; i < total; i += batch_size)); do
    batch=("${files[@]:i:batch_size}")
    echo "Processing batch: ${batch[*]}"
    # do something with the batch
    du -sh "${batch[@]}" 2>/dev/null
    echo "---"
done

Associative Arrays

Associative arrays use string keys instead of numeric indices. They require an explicit declare -A and are available in bash 4.0 and later.

declare -A server_ips

server_ips[web]="203.0.113.10"
server_ips[db]="203.0.113.20"
server_ips[cache]="203.0.113.30"

You can also initialize them inline.

declare -A ports=(
    [http]=80
    [https]=443
    [ssh]=22
    [mysql]=3306
)

Accessing Values

echo "${ports[ssh]}"       # 22
echo "${ports[https]}"     # 443

Keys and Iteration

# All keys
echo "${!ports[@]}"        # http https ssh mysql

# All values
echo "${ports[@]}"         # 80 443 22 3306

# Iterate over key-value pairs
for key in "${!ports[@]}"; do
    echo "$key -> ${ports[$key]}"
done

Checking If a Key Exists

if [[ -v ports[ssh] ]]; then
    echo "SSH port is ${ports[ssh]}"
fi

if [[ ! -v ports[ftp] ]]; then
    echo "FTP port not defined"
fi

Practical Example: Service Health Check

This script uses an associative array to map service names to their expected ports and checks whether each is listening.

#!/bin/bash
declare -A services=(
    [sshd]=22
    [nginx]=443
    [mysql]=3306
    [redis]=6379
)

for service in "${!services[@]}"; do
    port=${services[$service]}
    if ss -tlnp | grep -q ":${port} "; then
        echo "[OK]   $service is listening on port $port"
    else
        echo "[FAIL] $service is NOT listening on port $port"
    fi
done

Practical Example: Config File Parser

Associative arrays are a natural fit for reading key-value config files.

#!/bin/bash
declare -A config

while IFS='=' read -r key value; do
    # skip comments and blank lines
    [[ "$key" =~ ^#.*$ || -z "$key" ]] && continue
    # trim whitespace
    key=$(echo "$key" | xargs)
    value=$(echo "$value" | xargs)
    config[$key]="$value"
done < /etc/myapp.conf

echo "Database host: ${config[db_host]}"
echo "Database port: ${config[db_port]:-3306}"

The ${config[db_port]:-3306} syntax provides a default value when the key is missing, which we cover more in the parameter expansion article.

Things to Watch Out For

Quoting matters. Always use "${array[@]}" with double quotes when iterating. Without quotes, elements containing spaces will be split into separate words.

Sparse arrays. After using unset on an index, the array has gaps. Iteration with for item in "${array[@]}" still works, but direct index access may not hit what you expect.

Bash version. Associative arrays require bash 4.0 or later. Check with bash --version. Most modern Linux distributions ship bash 5.x, but if you are writing portable scripts, keep this in mind.

No nested arrays. Bash does not support arrays of arrays. If you need that, you are probably better off reaching for Python or another language.

Arrays are a powerful tool for organizing data in your shell scripts. Indexed arrays work well for ordered lists, while associative arrays are ideal for key-value lookups and configuration data.