Arrays let you store multiple values in a single variable and iterate over them cleanly. In production scripts, arrays are used for lists of servers to check, files to process, database names to back up, and job results to aggregate. Unlike most languages, bash arrays are sparse — indices don't need to be contiguous.
1
Declaring and populating arrays
BASH
# ── Declaration methods ───────────────────────────────────
# Method 1 — literal list
servers=("web-01" "web-02" "db-01" "cache-01")
# Method 2 — explicit declare
declare -a databases
databases=("myapp_prod" "myapp_stage" "analytics")
# Method 3 — one element at a time
ports[0]=3306
ports[1]=5432
ports[2]=6379
# Method 4 — from command output
log_files=($(ls /var/log/*.log)) # Word-split on spaces — risky
mapfile -t log_files < <(ls /var/log/*.log) # Safe — one per line
# Method 5 — mapfile (readarray) from file
mapfile -t hosts < /etc/hosts_list # Each line → one element
# Method 6 — split a string into array
csv="alpha,beta,gamma,delta"
IFS=',' read -ra parts <<< "${csv}"
echo "${parts[1]}" # beta
2
Accessing elements, length, and indices
BASH
servers=("web-01" "web-02" "db-01" "cache-01")
# Access single element
echo "${servers[0]}" # web-01 (first)
echo "${servers[2]}" # db-01 (third)
echo "${servers[-1]}" # cache-01 (last — bash 4.3+)
echo "${servers[-2]}" # db-01 (second from end)
# All elements
echo "${servers[@]}" # web-01 web-02 db-01 cache-01 (individually quoted)
echo "${servers[*]}" # web-01 web-02 db-01 cache-01 (joined with IFS)
# Length
echo "${#servers[@]}" # 4 (element count)
echo "${#servers[0]}" # 6 (length of element 0 string)
# All indices
echo "${!servers[@]}" # 0 1 2 3
# Slice — ${arr[@]:start:length}
echo "${servers[@]:1:2}" # web-02 db-01 (2 elements from index 1)
echo "${servers[@]:2}" # db-01 cache-01 (from index 2 to end)
3
Adding, modifying, and deleting elements
BASH
fruits=("apple" "banana" "cherry")
# Append one element
fruits+=("date")
echo "${fruits[@]}" # apple banana cherry date
# Append multiple
fruits+=("elderberry" "fig")
# Modify an element
fruits[1]="blueberry" # banana → blueberry
# Delete an element (leaves sparse gap)
unset 'fruits[2]' # Removes cherry; indices: 0,1,3,4,5
# Re-index after deletion (compact the array)
fruits=("${fruits[@]}") # Rebuild as 0-indexed: 0,1,2,3,4
# Delete entire array
unset fruits
# Concatenate two arrays
a=("one" "two")
b=("three" "four")
c=("${a[@]}" "${b[@]}") # one two three four
# Copy an array
original=("x" "y" "z")
copy=("${original[@]}")
4
Looping over arrays — safely
BASH
servers=("web-01" "web-02" "db-01 primary" "cache-01")
# ── Method 1: for-in with "$@" — handles spaces correctly
for server in "${servers[@]}"; do
echo "Checking: ${server}"
done
# ── Method 2: by index — useful when you need position
for i in "${!servers[@]}"; do
echo "[${i}] ${servers[$i]}"
done
# ── Method 3: C-style for loop
for (( i=0; i<${#servers[@]}; i++ )); do
echo "${servers[$i]}"
done
# ── Real-world: check all servers in parallel
check_server() {
local host="${1}"
if ping -c1 -W2 "${host}" >/dev/null 2>&1; then
echo " ✔ ${host} — reachable"
else
echo " ✘ ${host} — UNREACHABLE"
fi
}
hosts=("8.8.8.8" "1.1.1.1" "192.168.1.1")
echo "Checking connectivity..."
for host in "${hosts[@]}"; do
check_server "${host}" & # Run in parallel
done
wait # Wait for all background jobs
Terminal output
Key
Reachable
Unreachable
vriddh@prod-01:~/scripts$./check_hosts.sh
Checking connectivity...
✔ 8.8.8.8 — reachable
✔ 1.1.1.1 — reachable
✘ 192.168.1.1 — UNREACHABLE
█
5
Sorting, searching, and deduplicating
BASH
nums=(5 2 8 1 9 3)
words=("banana" "apple" "cherry" "apple" "date")
# Sort array (pipe through sort, rebuild)
mapfile -t sorted_nums < <(printf "%s\n" "${nums[@]}" | sort -n)
mapfile -t sorted_words < <(printf "%s\n" "${words[@]}" | sort)
echo "Sorted nums : ${sorted_nums[*]}" # 1 2 3 5 8 9
echo "Sorted words: ${sorted_words[*]}" # apple apple banana cherry date
# Deduplicate (sort -u)
mapfile -t unique < <(printf "%s\n" "${words[@]}" | sort -u)
echo "Unique: ${unique[*]}" # apple banana cherry date
# Search — check if element exists
contains() {
local target="${1}"; shift
local elem
for elem in "$@"; do
[[ "${elem}" == "${target}" ]] && return 0
done
return 1
}
if contains "cherry" "${words[@]}"; then
echo "Found cherry!"
fi
# Find index of element
find_index() {
local target="${1}"; local arr=("${@:2}")
for i in "${!arr[@]}"; do
[[ "${arr[$i]}" == "${target}" ]] && { echo "${i}"; return 0; }
done
echo "-1"; return 1
}
find_index "db-01" "${servers[@]}" # Returns index of "db-01"
6
Passing arrays to functions
BASH
# Bash cannot pass arrays by value — use nameref (bash 4.3+) or expansion
# Method 1 — expand elements as arguments (most common)
print_all() {
echo "Got ${#} items:"
for item in "$@"; do
echo " - ${item}"
done
}
my_arr=("alpha" "beta" "gamma")
print_all "${my_arr[@]}" # Passes elements as arguments
# Method 2 — nameref (bash 4.3+) — pass array by name
process_array() {
local -n arr_ref="${1}" # nameref — arr_ref IS the array
echo "Array has ${#arr_ref[@]} elements"
for item in "${arr_ref[@]}"; do
echo " ${item}"
done
}
process_array my_arr # Pass array NAME not expansion
# Method 3 — return array from function via global or nameref
get_active_dbs() {
local -n result="${1}" # Output array by nameref
result=()
while IFS= read -r db; do
result+=("${db}")
done < <(mysql -se "SHOW DATABASES" 2>/dev/null)
}
declare -a active_dbs
get_active_dbs active_dbs
echo "Found: ${active_dbs[*]}"
7
Real-world — multi-database backup using arrays
BASH
#!/usr/bin/env bash
# multi_db_backup.sh — Back up multiple databases using arrays
set -euo pipefail
readonly BACKUP_DIR="/var/backups/mysql"
readonly DATE=$(date '+%Y%m%d_%H%M')
readonly RETENTION_DAYS=7
DATABASES=("myapp_prod" "analytics" "user_data" "audit_logs")
success=()
failed=()
backup_db() {
local db="${1}"
local outfile="${BACKUP_DIR}/${db}_${DATE}.sql.gz"
if mysqldump "${db}" | gzip > "${outfile}"; then
echo " ✔ ${db} → $(du -sh "${outfile}" | cut -f1)"
return 0
else
echo " ✘ ${db} — FAILED"
return 1
fi
}
mkdir -p "${BACKUP_DIR}"
echo "Starting backup of ${#DATABASES[@]} databases..."
for db in "${DATABASES[@]}"; do
if backup_db "${db}"; then
success+=("${db}")
else
failed+=("${db}")
fi
done
echo "────────────────────────────────"
echo "✔ Succeeded: ${#success[@]} — ${success[*]:-none}"
echo "✘ Failed : ${#failed[@]} — ${failed[*]:-none}"
# Cleanup old backups
find "${BACKUP_DIR}" -name "*.sql.gz" -mtime +"${RETENTION_DAYS}" -delete
[[ ${#failed[@]} -gt 0 ]] && exit 1
exit 0
Terminal output
vriddh@prod-01:~/scripts$./multi_db_backup.sh
Starting backup of 4 databases...
✔ myapp_prod → 245M
✔ analytics → 1.2G
✘ user_data — FAILED
✔ audit_logs → 89M
────────────────────────────────
✔ Succeeded: 3 — myapp_prod analytics audit_logs
✘ Failed : 1 — user_data
█
✔ Array rules to live by — Always quote array expansions:
"${arr[@]}" not ${arr[@]}. Use mapfile -t to read files/commands into arrays safely. Use local -n nameref to pass arrays to functions. Track success/failure in separate arrays for clean reporting. Never use ${arr[*]} in loops — use "${arr[@]}".⚠ The glob trap —
arr=($(ls *.log)) splits on spaces and does glob expansion. A filename with a space breaks it. Always use mapfile -t arr < <(command) for safe array population from commands.