Bash Arrays
Work with indexed and associative arrays in Bash — creation, access, slicing, looping, and reading files with mapfile.
Indexed Arrays
Arrays solve the problem of managing a collection of values as a single unit. Without arrays, you would have to create separate variables like server1, server2, server3 — which makes iteration impossible and maintenance painful. Arrays let you loop over a list, pass it to a function, and manipulate it as a whole.
# Declare and initialize in one step
fruits=("apple" "banana" "cherry" "date")
# Declare first, then assign by index
declare -a servers
servers[0]="web01"
servers[1]="web02"
servers[2]="db01"
# Access by index
echo "${fruits[0]}" # apple
echo "${fruits[2]}" # cherry
echo "${fruits[-1]}" # date (negative index counts from the end)
# All elements — always use [@] in double quotes to handle spaces correctly
echo "${fruits[@]}" # apple banana cherry date
# Number of elements
echo "${#fruits[@]}" # 4
# All indices (useful when the array has gaps after unset)
echo "${!fruits[@]}" # 0 1 2 3
Adding and Removing Elements
Arrays are dynamic — you can grow them at any time with += and shrink them with unset. One important detail: unset on an element leaves a gap in the indices; it does not shift subsequent elements down.
fruits=("apple" "banana" "cherry")
# Append a single element
fruits+=("date")
echo "${fruits[@]}" # apple banana cherry date
# Append multiple elements at once
fruits+=("elderberry" "fig")
echo "${#fruits[@]}" # 6
# Remove element by index — leaves a gap at index 1
unset fruits[1]
echo "${fruits[@]}" # apple cherry date elderberry fig
echo "${!fruits[@]}" # 0 2 3 4 5 (index 1 is gone)
# Re-index to close the gap
fruits=("${fruits[@]}")
echo "${!fruits[@]}" # 0 1 2 3 4 (continuous again)
Slicing
Array slicing uses ${array[@]:start:length} — the same syntax as string substring extraction, applied to arrays. This is useful for processing parts of an argument list or splitting a large array into chunks.
letters=("a" "b" "c" "d" "e" "f")
# ${array[@]:start:length}
echo "${letters[@]:1:3}" # b c d (start at index 1, take 3 elements)
echo "${letters[@]:3}" # d e f (from index 3 to the end)
echo "${letters[@]: -2}" # e f (last 2 elements)
Looping Over Arrays
Looping over arrays is the most common array operation. Always use "${array[@]}" — the double quotes prevent word splitting on elements that contain spaces.
servers=("web01" "web02" "db01" "cache01")
# Standard loop — handles elements with spaces correctly
for server in "${servers[@]}"; do
echo "Pinging $server..."
done
# Loop with index — useful when you need the position
for i in "${!servers[@]}"; do
echo "$i: ${servers[$i]}"
done
# 0: web01
# 1: web02
# 2: db01
# 3: cache01
# C-style loop — useful when you need to manipulate the counter
for (( i = 0; i < ${#servers[@]}; i++ )); do
echo "Server $i: ${servers[$i]}"
done
Associative Arrays
Associative arrays use arbitrary string keys instead of integer indices. They behave like a dictionary or hash map. They must be explicitly declared with declare -A — without the declaration, Bash silently converts the string keys to integers.
declare -A config=(
[host]="localhost"
[port]="5432"
[database]="myapp"
[user]="dbadmin"
)
# Access by key
echo "${config[host]}" # localhost
echo "${config[port]}" # 5432
# Add a new key or update an existing one
config[password]="s3cr3t"
config[port]="5433"
# All keys — order is not guaranteed in associative arrays
echo "${!config[@]}" # host port database user password
# All values
echo "${config[@]}" # localhost 5433 myapp dbadmin s3cr3t
# Number of entries
echo "${#config[@]}" # 5
# Check if a key exists with -v
if [[ -v config[password] ]]; then
echo "Password is set"
fi
Iterating over an associative array:
declare -A http_codes=(
[200]="OK"
[201]="Created"
[400]="Bad Request"
[401]="Unauthorized"
[404]="Not Found"
[500]="Internal Server Error"
)
for code in "${!http_codes[@]}"; do
printf "HTTP %s: %s\n" "$code" "${http_codes[$code]}"
done
mapfile / readarray
mapfile (also called readarray) reads lines from stdin or a file directly into an indexed array. This is the correct way to populate an array from command output — it handles spaces in values and avoids the subshell problem of using a pipe.
# Read a file into an array (one element per line)
mapfile -t lines < /etc/hosts
echo "Lines: ${#lines[@]}"
echo "First: ${lines[0]}"
# Read command output into an array
mapfile -t containers < <(docker ps --format '{{.Names}}')
for c in "${containers[@]}"; do
echo "Container: $c"
done
-t strips the trailing newline from each element. Always use it — without it, every element ends with \n and string comparisons will silently fail.
Array Operations
Filtering an array
Build a new array containing only elements that match a condition:
servers=("web01" "web02" "db01" "db02" "cache01")
db_servers=()
for s in "${servers[@]}"; do
[[ "$s" == db* ]] && db_servers+=("$s") # keep only db* servers
done
echo "${db_servers[@]}" # db01 db02
Deduplicating an array
Bash has no built-in dedup, but an associative array works as a seen-set:
dedup() {
declare -A seen
local -a result=()
for item in "$@"; do
if [[ ! -v seen["$item"] ]]; then
seen["$item"]=1
result+=("$item")
fi
done
echo "${result[@]}"
}
items=("a" "b" "a" "c" "b" "d")
unique=($(dedup "${items[@]}"))
echo "${unique[@]}" # a b c d
Joining array elements
join_by() {
local delimiter="$1"; shift
local first="$1"; shift
printf '%s' "$first" "${@/#/$delimiter}"
}
arr=("one" "two" "three")
echo "$(join_by ', ' "${arr[@]}")" # one, two, three
echo "$(join_by '|' "${arr[@]}")" # one|two|three
Sorting an array
Bash has no built-in sort for arrays. The standard approach is to pipe the elements through the sort command and read them back with mapfile:
fruits=("banana" "apple" "cherry" "date")
# Sort ascending
mapfile -t sorted < <(printf '%s\n' "${fruits[@]}" | sort)
echo "${sorted[@]}" # apple banana cherry date
# Sort descending
mapfile -t sorted_desc < <(printf '%s\n' "${fruits[@]}" | sort -r)
echo "${sorted_desc[@]}" # date cherry banana apple
# Sort numerically
nums=(10 2 30 4 20)
mapfile -t sorted_nums < <(printf '%s\n' "${nums[@]}" | sort -n)
echo "${sorted_nums[@]}" # 2 4 10 20 30
Searching an array
contains() {
local needle="$1"; shift
for item in "$@"; do
[[ "$item" == "$needle" ]] && return 0 # found
done
return 1 # not found
}
fruits=("apple" "banana" "cherry")
if contains "banana" "${fruits[@]}"; then
echo "Found banana"
fi
if ! contains "mango" "${fruits[@]}"; then
echo "No mango"
fi
Passing Arrays to Functions
Bash does not pass arrays by value. If you use "${array[@]}" as a function argument, the elements arrive as separate positional parameters — the array structure is lost. To pass an array and preserve it, use a nameref.
print_array() {
local -n arr="$1" # nameref — arr is an alias for the named variable
for i in "${!arr[@]}"; do
echo " [$i] = ${arr[$i]}"
done
}
fruits=("apple" "banana" "cherry")
print_array fruits # pass the variable name, not its contents
What’s Next
The next tutorial covers file operations — testing file attributes, safely copying and moving files, and using find and stat.