Arrays
Indexed and associative arrays in Bash — declaring, accessing, looping, slicing, and the quoting rules that matter.
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 -Ais mandatory for associative arrays; forgetting it is a silent failure, not an error. unseton an array element leaves a gap in indices — re-index withARR=("${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 -Afor associative arrays, silently getting indexed-array behavior instead. - Not realizing
unseton 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