Guide Bash-Scripting Intermediate

Arrays

Indexed and associative arrays in Bash — declaring, accessing, looping, slicing, and the quoting rules that matter.

3 min read

Indexed Arrays

SERVERS=("web1" "web2" "web3")
SERVERS[3]="web4"                  # append at a specific index
SERVERS+=("web5")                    # append at the end

echo "${SERVERS[0]}"          # "web1" — first element
echo "${SERVERS[-1]}"           # "web5" — last element (Bash 4.3+)
echo "${SERVERS[@]}"               # all elements
echo "${#SERVERS[@]}"                 # 5 — number of elements
echo "${!SERVERS[@]}"                   # 0 1 2 3 4 — all indices

Looping Over an Array

for server in "${SERVERS[@]}"; do
    echo "Checking $server"
done

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

“${arr[@]}” vs “${arr[*]}” — The Critical Difference

ARR=("one two" "three")

for x in "${ARR[@]}"; do echo "[$x]"; done
# [one two]
# [three]      <- @ preserves each element as a SEPARATE word, correctly

for x in "${ARR[*]}"; do echo "[$x]"; done
# [one two three]   <- * joins everything into ONE word — usually not what you want

Rule: always use "${ARR[@]}" (quoted, with @) when iterating — it’s the only form that correctly preserves elements containing spaces.

Associative Arrays (Bash 4+)

declare -A CONFIG
CONFIG[environment]="production"
CONFIG[region]="us-east-1"
CONFIG[instance_type]="t3.medium"

echo "${CONFIG[environment]}"       # "production"
echo "${!CONFIG[@]}"                   # all KEYS: environment region instance_type
echo "${CONFIG[@]}"                       # all VALUES

for key in "${!CONFIG[@]}"; do
    echo "$key = ${CONFIG[$key]}"
done

declare -A is required for associative arrays — without it, Bash creates a regular indexed array instead, and string keys silently don’t work as expected.

Slicing Arrays

ARR=(a b c d e)
echo "${ARR[@]:1:3}"      # "b c d" — start at index 1, take 3 elements
echo "${ARR[@]:2}"          # "c d e" — from index 2 to the end

Removing Elements

ARR=(a b c d)
unset 'ARR[1]'         # removes index 1, but LEAVES A GAP — indices become 0, 2, 3
echo "${ARR[@]}"           # "a c d"
echo "${!ARR[@]}"             # "0 2 3" — note index 1 is missing

ARR=("${ARR[@]}")               # re-index to close the gap
echo "${!ARR[@]}"                  # "0 1 2"

Checking If an Array Contains a Value

ARR=("dev" "staging" "production")
TARGET="staging"

if [[ " ${ARR[*]} " == *" $TARGET "* ]]; then
    echo "Found $TARGET"
fi

# Cleaner: loop-based check
contains() {
    local target="$1"; shift
    for item in "$@"; do
        [[ "$item" == "$target" ]] && return 0
    done
    return 1
}
contains "$TARGET" "${ARR[@]}" && echo "Found"

A Real Example: Deploying to Multiple Servers

SERVERS=("web1.example.com" "web2.example.com" "web3.example.com")

for server in "${SERVERS[@]}"; do
    echo "Deploying to $server..."
    ssh "deploy@$server" "cd /app && git pull && systemctl restart myapp" \
        || { echo "Deploy FAILED on $server" >&2; exit 1; }
done
echo "Deployed to all ${#SERVERS[@]} servers"

Production Considerations

  • Always use "${ARR[@]}" (double-quoted, @) for iteration — ${ARR[*]} or unquoted expansion breaks on elements containing spaces.
  • Remember declare -A is mandatory for associative arrays; forgetting it is a silent failure, not an error.
  • unset on an array element leaves a gap in indices — re-index with ARR=("${ARR[@]}") if contiguous indices matter downstream.

Quick Interview Answer

“Bash has indexed arrays (ARR=(a b c)) and, since Bash 4, associative arrays (declare -A CONFIG) for key-value data. \"${ARR[@]}\" is the correct way to iterate — it preserves each element as a separate word even if it contains spaces, unlike ${ARR[*]} which joins everything into one string. ${#ARR[@]} gives the count, and ${!ARR[@]} gives the indices/keys.”

Common Mistakes

  • Using ${ARR[*]} instead of "${ARR[@]}" when iterating, breaking on elements with spaces.
  • Forgetting declare -A for associative arrays, silently getting indexed-array behavior instead.
  • Not realizing unset on an array element leaves an index gap rather than shifting subsequent elements down.

Add More Questions to This Guide

Know a question that should be here? Share it and help the community!

Open Google Form