Shell Scripting Arrays: Bash Indexed & Associative Guide
Variables store single values. Arrays store collections. When you're managing lists of users, processing multiple files, or storing configuration key-value pairs, arrays are the data structure that makes your shell scripts truly capable of handling real-world complexity.
Bash supports two types of arrays: indexed arrays (ordered lists accessed by number) and associative arrays (key-value pairs accessed by string). In this guide, we'll explore both with practical examples you can use immediately.
Indexed Arrays
Indexed arrays are ordered collections where each element is accessed by a numeric index, starting at 0.
Creating Indexed Arrays
1#!/bin/bash 2 3# Method 1: Direct assignment 4fruits=("apple" "banana" "cherry" "date") 5 6# Method 2: Individual indices 7servers[0]="web01" 8servers[1]="web02" 9servers[2]="db01" 10 11# Method 3: Mixed (sparse arrays are allowed) 12ports[0]=80 13ports[10]=443 14ports[20]=8080
Accessing Elements
1#!/bin/bash 2 3fruits=("apple" "banana" "cherry" "date") 4 5echo "First fruit: ${fruits[0]}" # apple 6echo "Second fruit: ${fruits[1]}" # banana 7echo "Last fruit: ${fruits[-1]}" # date (negative index counts from end) 8 9# Access all elements 10echo "All fruits: ${fruits[@]}" # apple banana cherry date 11echo "All fruits: ${fruits[*]}" # apple banana cherry date
Important: Always use curly braces
${array[index]}when accessing array elements. Without braces, Bash interpretsfruits[0]as a string literal.
Array Length and Indices
1#!/bin/bash 2 3fruits=("apple" "banana" "cherry") 4 5echo "Total fruits: ${#fruits[@]}" # 3 6echo "Length of first element: ${#fruits[0]}" # 5 (length of "apple") 7 8# Get all indices (useful for sparse arrays) 9echo "Indices: ${!fruits[@]}" # 0 1 2
Modifying Arrays
1#!/bin/bash 2 3fruits=("apple" "banana" "cherry") 4 5# Change an element 6fruits[1]="blueberry" 7echo "${fruits[@]}" # apple blueberry cherry 8 9# Append elements 10fruits+=("date") 11fruits+=("elderberry" "fig") 12echo "${fruits[@]}" # apple blueberry cherry date elderberry fig 13 14# Remove an element 15unset fruits[2] 16echo "${fruits[@]}" # apple blueberry date elderberry fig 17 18# Remove entire array 19unset fruits
Iterating Over Arrays
Method 1: Iterate over values
1#!/bin/bash 2 3fruits=("apple" "banana" "cherry") 4 5for fruit in "${fruits[@]}"; do 6 echo "I like $fruit" 7done
Method 2: Iterate over indices
1#!/bin/bash 2 3fruits=("apple" "banana" "cherry") 4 5for i in "${!fruits[@]}"; do 6 echo "$i: ${fruits[$i]}" 7done
Always quote
"${fruits[@]}"when iterating. Without quotes, elements containing spaces will split into separate words.
Slicing Arrays
Extract portions of an array using ${array[@]:start:length}:
1#!/bin/bash 2 3numbers=(10 20 30 40 50 60 70 80) 4 5echo "${numbers[@]:2:3}" # 30 40 50 6echo "${numbers[@]: -3}" # 60 70 80 (last 3 elements)
Practical Example: Batch File Processor
1#!/bin/bash 2 3# Process all .log files in a directory 4log_files=("/var/log/syslog" "/var/log/auth.log" "/var/log/kern.log") 5 6for logfile in "${log_files[@]}"; do 7 if [ -f "$logfile" ]; then 8 lines=$(wc -l < "$logfile") 9 size=$(stat -c%s "$logfile" 2>/dev/null || stat -f%z "$logfile") 10 echo "$(basename "$logfile"): $lines lines, $size bytes" 11 else 12 echo "Warning: $logfile not found" >&2 13 fi 14done
Associative Arrays (Bash 4+)
Associative arrays store data as key-value pairs, similar to dictionaries in Python or hashes in Perl. They require Bash version 4 or higher.
Check your Bash version:
bash --version. macOS ships with Bash 3.2 by default — upgrade via Homebrew or usebrew install bash.
Declaring Associative Arrays
You must use declare -A before assigning values:
1#!/bin/bash 2 3declare -A user 4 5user[name]="Alice" 6user[age]=30 7user[city]="New York" 8user[role]="Admin"
Accessing and Modifying
1#!/bin/bash 2 3declare -A user 4 5user[name]="Alice" 6user[email]="alice@example.com" 7 8echo "Name: ${user[name]}" 9echo "Email: ${user[email]}" 10 11# Change a value 12user[role]="SuperAdmin" 13 14# Add new keys dynamically 15user[department]="Engineering"
Iterating Over Associative Arrays
1#!/bin/bash 2 3declare -A config 4 5config[host]="localhost" 6config[port]=5432 7config[database]="myapp" 8config[user]="dbadmin" 9 10# Iterate over keys 11echo "=== Configuration Keys ===" 12for key in "${!config[@]}"; do 13 echo "$key: ${config[$key]}" 14done 15 16# Check if key exists 17if [ -v config[password] ]; then 18 echo "Password is set" 19else 20 echo "Password not configured" 21fi
Note: Associative arrays are unordered. The iteration order is not guaranteed.
Checking Array Length
1#!/bin/bash 2 3declare -A server 4 5server[web]="192.168.1.10" 6server[db]="192.168.1.20" 7server[cache]="192.168.1.30" 8 9echo "Total servers: ${#server[@]}" # 3 10echo "Server IPs: ${server[@]}" # 192.168.1.10 192.168.1.20 192.168.1.30
Removing Elements
1#!/bin/bash 2 3declare -A env 4 5env[dev]="localhost" 6env[staging]="staging.example.com" 7env[prod]="prod.example.com" 8 9unset env[dev] # Remove single key 10echo "${#env[@]}" # 2 11 12unset env # Remove entire array
Array Operations and Techniques
Reading Lines into an Array
1#!/bin/bash 2 3# Read file lines into array 4mapfile -t lines < config.txt 5echo "Total lines: ${#lines[@]}" 6 7# Read command output into array 8mapfile -t processes < <(ps -eo comm=) 9echo "Running processes: ${#processes[@]}" 10 11# Alternative: read with IFS 12IFS=$'\n' read -r -d '' -a files < <(find . -name "*.sh" -type f) 13echo "Shell scripts found: ${#files[@]}"
Joining Array Elements into a String
1#!/bin/bash 2 3fruits=("apple" "banana" "cherry") 4 5# Join with comma 6csv=$(IFS=,; echo "${fruits[*]}") 7echo "$csv" # apple,banana,cherry 8 9# Join with pipe 10pipeline=$(IFS='|'; echo "${fruits[*]}") 11echo "$pipeline" # apple|banana|cherry
Checking if Array is Empty
1#!/bin/bash 2 3files=() 4 5if [ ${#files[@]} -eq 0 ]; then 6 echo "No files to process" 7fi 8 9# Alternative 10if [ -z "${files[*]}" ]; then 11 echo "Array is empty" 12fi
Copying Arrays
1#!/bin/bash 2 3original=("a" "b" "c") 4 5# Correct way to copy 6copy=("${original[@]}") 7 8# Modify copy without affecting original 9copy+=("d") 10 11echo "Original: ${original[@]}" # a b c 12echo "Copy: ${copy[@]}" # a b c d
Real-World Examples
Server Inventory Manager
1#!/bin/bash 2 3declare -A servers 4 5servers[web01]="192.168.1.10:running:Ubuntu" 6servers[web02]="192.168.1.11:stopped:CentOS" 7servers[db01]="192.168.1.20:running:Debian" 8 9echo "=== Server Inventory ===" 10printf "%-10s %-15s %-10s %-10s\n" "NAME" "IP" "STATUS" "OS" 11printf "%-10s %-15s %-10s %-10s\n" "----" "--" "------" "--" 12 13for server in "${!servers[@]}"; do 14 IFS=':' read -r ip status os <<< "${servers[$server]}" 15 printf "%-10s %-15s %-10s %-10s\n" "$server" "$ip" "$status" "$os" 16done
Output:
=== Server Inventory ===
NAME IP STATUS OS
---- -- ------ --
web01 192.168.1.10 running Ubuntu
web02 192.168.1.11 stopped CentOS
db01 192.168.1.20 running Debian
Configuration Parser
1#!/bin/bash 2 3declare -A config 4 5# Parse key=value pairs from a file 6while IFS='=' read -r key value; do 7 # Skip comments and empty lines 8 [[ "$key" =~ ^[[:space:]]*# ]] && continue 9 [[ -z "$key" ]] && continue 10 11 # Trim whitespace 12 key=$(echo "$key" | xargs) 13 value=$(echo "$value" | xargs) 14 15 config["$key"]="$value" 16done < app.conf 17 18echo "Application: ${config[app_name]}" 19echo "Version: ${config[version]}" 20echo "Port: ${config[port]}" 21 22# Validate required keys 23required_keys=("app_name" "port" "database_url") 24for key in "${required_keys[@]}"; do 25 if [ -z "${config[$key]:-}" ]; then 26 echo "Error: Missing required config: $key" >&2 27 exit 1 28 fi 29done
Multi-Environment Deployer
1#!/bin/bash 2 3declare -A deploy_config 4 5deploy_config[dev_branch]="develop" 6deploy_config[staging_branch]="release" 7deploy_config[prod_branch]="main" 8 9deploy_config[dev_server]="dev.example.com" 10deploy_config[staging_server]="staging.example.com" 11deploy_config[prod_server]="prod.example.com" 12 13deploy_config[dev_port]=22 14deploy_config[staging_port]=22 15deploy_config[prod_port]=2222 16 17environment="${1:-dev}" 18 19if [ -z "${deploy_config[${environment}_branch]:-}" ]; then 20 echo "Unknown environment: $environment" 21 echo "Available: dev, staging, prod" 22 exit 1 23fi 24 25branch="${deploy_config[${environment}_branch]}" 26server="${deploy_config[${environment}_server]}" 27port="${deploy_config[${environment}_port]}" 28 29echo "Deploying $branch to $environment..." 30echo "Target: $server:$port"
Indexed vs Associative Arrays: When to Use What
| Feature | Indexed Arrays | Associative Arrays |
|---|---|---|
| Syntax | array=(a b c) | declare -A array |
| Index type | Integers (0, 1, 2...) | Strings ("key", "name") |
| Order | Preserved | Not guaranteed |
| Use case | Lists, sequences, file collections | Configurations, mappings, lookups |
| Bash version | All versions | Bash 4+ |
| Sparse support | Yes | Yes |
Best Practices for Using Arrays
- Always quote array expansions —
"${array[@]}"not${array[@]} - Use
declare -Afor associative arrays — forgetting this creates weird indexed behavior - Check if elements exist with
[ -v array[index] ]before accessing - Use
mapfile -tinstead of loops to read files into arrays - Prefer indexed arrays for ordered data and associative arrays for lookups
- Unset elements properly —
unset array[index]for single items,unset arrayfor the whole thing - Document sparse arrays if you use non-sequential indices
- Check Bash version before using associative arrays in shared scripts
Quick Reference
1# Indexed arrays 2arr=(a b c) # Create 3arr[0]="x" # Set element 4${arr[0]} # Get element 5${arr[@]} # All elements 6${arr[*]} # All elements (single string) 7${#arr[@]} # Array length 8${!arr[@]} # All indices 9${arr[@]:1:2} # Slice 10arr+=("d") # Append 11unset arr[1] # Remove element 12unset arr # Remove array 13 14# Associative arrays 15declare -A hash # Declare 16hash[key]="value" # Set 17${hash[key]} # Get 18${!hash[@]} # All keys 19${hash[@]} # All values 20${#hash[@]} # Number of pairs 21[ -v hash[key] ] # Check if key exists 22 23# Read into array 24mapfile -t arr < file.txt 25IFS=$'\n' read -r -d '' -a arr < <(command) 26 27# Join array 28csv=$(IFS=,; echo "${arr[*]}")
Arrays transform shell scripting from simple command execution into real data processing. Whether you're managing server inventories, parsing configurations, or batch-processing files, mastering indexed and associative arrays is essential for writing professional-grade Bash scripts.
Next up: String manipulation — slicing, substitution, and pattern matching techniques that every scripter needs.