Shell Scripting Bash Arrays Basics May 2026

Shell Scripting Arrays

Master indexed arrays in bash — declaration, access, append, delete, slice, loop, sort, and pass to functions. Arrays are essential for managing lists of servers, files, jobs, and database names in production scripts.

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.

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
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)
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[@]}")
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
Key Reachable Unreachable
bash — server check
vriddh@prod-01:~/scripts$./check_hosts.sh
Checking connectivity...
✔ 8.8.8.8 — reachable
✔ 1.1.1.1 — reachable
✘ 192.168.1.1 — UNREACHABLE
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"
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[*]}"
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
bash — multi_db_backup.sh
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 traparr=($(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.