Bash arrays and loops are two of the most powerful tools in shell scripting. On their own, each command in a Bash script runs a single operation on a single piece of data. Arrays let you store and manage multiple values under one variable name. Loops let you repeat operations across those values automatically. Together, they allow you to write scripts that handle real-world tasks: processing lists of files, iterating over server names, transforming data from configuration files, or running the same operation across hundreds of items without repetition.
This guide covers how Bash arrays work, the types of loops available in Bash, and practical patterns that combine both for common scripting tasks.
Bash Arrays
Bash supports two types of arrays: indexed arrays (standard arrays with numeric indices starting at 0) and associative arrays (key-value pairs, available in Bash 4 and later).
Creating and Populating an Indexed Array
You can define an array in several ways:
# Define an array with values on a single line
servers=("web01" "web02" "db01" "cache01")
# Define an array and add values one by one
packages=()
packages+=("nginx")
packages+=("curl")
packages+=("git")
Access individual elements by index:
echo "${servers[0]}" # Outputs: web01
echo "${servers[2]}" # Outputs: db01
Access all elements with @ or *:
echo "${servers[@]}" # Outputs all elements separated by spaces
Get the number of elements in an array:
echo "${#servers[@]}" # Outputs: 4
Creating an Associative Array
Associative arrays use string keys instead of numeric indices. They must be declared explicitly:
declare -A config
config["host"]="192.168.1.100"
config["port"]="8080"
config["user"]="deploy"
echo "${config[host]}" # Outputs: 192.168.1.100
Access all keys:
echo "${!config[@]}" # Outputs: host port user
Bash Loops
Bash provides three main loop types: for, while, and until. Each suits different patterns of iteration.
The for Loop
The most common loop for iterating over a list of values or array elements:
# Loop over a static list
for fruit in apple banana cherry; do
echo "Fruit: $fruit"
done
# Loop over an array
for server in "${servers[@]}"; do
echo "Pinging: $server"
ping -c 1 "$server" > /dev/null 2>&1 && echo "$server is up" || echo "$server is down"
done
The C-Style for Loop (Numeric Range)
For looping a specific number of times with a counter:
for ((i = 1; i <= 10; i++)); do
echo "Iteration $i"
done
The while Loop
Repeats as long as a condition evaluates as true. Useful for reading input line by line or waiting for a condition:
# Read each line of a file into a variable
while IFS= read -r line; do
echo "Line: $line"
done < "/etc/hosts"
The until Loop
The inverse of while — repeats until the condition becomes true:
count=0
until [ $count -ge 5 ]; do
echo "Count is $count"
((count++))
done
Looping Over Array Indices
Sometimes you need both the index and the value. Use ${!array[@]} to iterate over indices:
files=("report.pdf" "data.csv" "config.yaml")
for i in "${!files[@]}"; do
echo "File $i: ${files[$i]}"
done
# Outputs:
# File 0: report.pdf
# File 1: data.csv
# File 2: config.yaml
Practical Scripting Patterns
Batch File Processing
Process every file matching a pattern in a directory:
#!/bin/bash
for file in /var/log/*.log; do
echo "Processing: $file"
gzip "$file"
done
Reading Command Output into an Array
Capture the output of a command as an array for processing:
# Store all running process names in an array
mapfile -t processes < <(ps -eo comm= | sort -u)
for proc in "${processes[@]}"; do
echo "$proc"
done
The mapfile (or readarray) command reads lines from standard input into an indexed array. The -t flag removes trailing newlines from each element.
Building a Simple Retry Loop
Retry a command up to a maximum number of attempts:
#!/bin/bash
max_attempts=5
attempt=1
while [ $attempt -le $max_attempts ]; do
echo "Attempt $attempt of $max_attempts..."
curl -s "https://example.com/api/health" && break
echo "Failed. Retrying in 5 seconds..."
sleep 5
((attempt++))
done
if [ $attempt -gt $max_attempts ]; then
echo "Service did not respond after $max_attempts attempts."
exit 1
fi
Looping Over an Associative Array
Process key-value configuration pairs:
declare -A env_vars
env_vars["DB_HOST"]="localhost"
env_vars["DB_PORT"]="5432"
env_vars["APP_ENV"]="production"
for key in "${!env_vars[@]}"; do
echo "export $key='${env_vars[$key]}'"
done
Loop Control: break and continue
Use break to exit a loop early and continue to skip the current iteration and move to the next:
for number in 1 2 3 4 5 6 7 8 9 10; do
if [ $number -eq 5 ]; then
continue # Skip 5
fi
if [ $number -eq 8 ]; then
break # Stop at 8
fi
echo "$number"
done
# Outputs: 1 2 3 4 6 7
Common Mistakes and How to Avoid Them
Always Quote Array Expansions
Unquoted array expansions cause word splitting when elements contain spaces. Always use double quotes:
# Correct
for item in "${array[@]}"; do
echo "$item"
done
# Incorrect (breaks if any element contains spaces)
for item in ${array[@]}; do
echo "$item"
done
Use mapfile Instead of Subshell Loops for Reading Files
Avoid for line in $(cat file) — it splits on whitespace and is slower. Use while read or mapfile instead for reliable line-by-line processing.
Check Array Length Before Accessing Elements
Accessing an index that does not exist produces an empty string silently. Check length with ${#array[@]} before assuming elements exist.
Bash arrays and loops are the building blocks that transform simple one-liners into maintainable, reusable scripts. With indexed arrays for sequential data, associative arrays for structured configuration, and loops for repetition, you can automate complex multi-step workflows that would otherwise require manual repetition or more complex scripting languages.