Arrays let a Bash script keep lists, such as servers, databases, files and options, without fragile space-separated strings. Bash has indexed arrays (numbered from 0) and, since Bash 4, associative arrays (key → value maps).
Indexed arrays
servers=(web1 web2 db1)
servers[3]=cache1 # set a specific index
servers+=(worker1) # append
Access:
echo "${servers[0]}" # web1
echo "${servers[-1]}" # last element (Bash 4.3+)
echo "${servers[@]}" # all elements
echo "${#servers[@]}" # number of elements
echo "${!servers[@]}" # all indexes: 0 1 2 3 4
The braces are required. $servers[0] prints the first element followed by a literal [0].
Looping
for s in "${servers[@]}"; do
ssh -n "$s" uptime
done
Always quote "${arr[@]}". Each element then stays one argument, even if it contains spaces. "${arr[*]}" joins all elements into one string, separated by the first character of IFS:
files=("my report.pdf" "notes.txt")
printf '[%s]\n' "${files[@]}" # [my report.pdf] [notes.txt]
printf '[%s]\n' "${files[*]}" # [my report.pdf notes.txt]
Loop with indexes:
for i in "${!servers[@]}"; do
echo "$i: ${servers[$i]}"
done
Slicing, removing and replacing
echo "${servers[@]:1:2}" # 2 elements starting at index 1
unset 'servers[1]' # delete element 1 (leaves a gap in the indexes)
servers=("${servers[@]}") # re-index to remove gaps
echo "${servers[@]/web/app}" # replace text in every element (output only)
Remove an element by value:
for i in "${!servers[@]}"; do
[[ "${servers[$i]}" == "db1" ]] && unset 'servers[i]'
done
Check whether an array contains a value
contains() {
local needle="$1"; shift
local x
for x in "$@"; do [[ "$x" == "$needle" ]] && return 0; done
return 1
}
contains "web2" "${servers[@]}" && echo "found"
Fill arrays from commands and files
mapfile -t lines < hosts.txt # one element per line
mapfile -t dbs < <(mysql -N -e 'SHOW DATABASES') # from command output
readarray -t users < <(cut -d: -f1 /etc/passwd) # readarray = mapfile
files=(/var/backups/*.sql.gz) # from a glob
Avoid arr=($(command)). It splits on spaces and expands globs. mapfile -t is the safe way.
For NUL-separated output (filenames):
mapfile -d '' -t files < <(find /srv -name '*.log' -print0)
Arrays for command arguments
This is one of the most useful patterns. Building command options in a string breaks on spaces, while an array doesn’t:
opts=(-avz --delete --exclude='cache/')
[[ "$DRY_RUN" == 1 ]] && opts+=(--dry-run)
rsync "${opts[@]}" /var/www/site/ backup:/srv/site/
Associative arrays (maps)
Declare them first:
declare -A port
port[http]=80
port[https]=443
port[ssh]=22
# or all at once
declare -A owner=([shop]=alice [blog]=bob)
Use them:
echo "${port[https]}" # 443
echo "${!port[@]}" # keys (in no particular order)
echo "${#port[@]}" # count
[[ -v port[ftp] ]] || echo "no ftp entry" # key exists? (Bash 4.2+)
unset 'port[ssh]'
for k in "${!port[@]}"; do
printf '%-6s %s\n' "$k" "${port[$k]}"
done
Keys come out in arbitrary order. Pipe through sort if order matters.
Counting things, for example requests per IP:
declare -A hits
while read -r ip _; do
(( hits[$ip]++ ))
done < /var/log/nginx/access.log
for ip in "${!hits[@]}"; do echo "${hits[$ip]} $ip"; done | sort -rn | head
sort | uniq -c is simpler and faster for big logs. See sort and uniq.
Passing arrays to functions
Pass the elements as arguments:
deploy_all() { for h in "$@"; do echo "deploying to $h"; done; }
deploy_all "${servers[@]}"
Or by name, using a nameref (Bash 4.3+):
print_map() {
local -n m="$1"
for k in "${!m[@]}"; do echo "$k=${m[$k]}"; done
}
print_map port
Limits
- No nested arrays (arrays of arrays). For structured data, use
jqwith JSON, or another language. - Associative arrays need Bash 4+. Older macOS ships Bash 3.2, while servers have Bash 5.
shand dash have no arrays at all. Use#!/usr/bin/env bash.
Quick reference
| Task | Syntax |
|---|---|
| Create | a=(x y z) |
| Append | a+=(w) |
| Element | "${a[1]}" |
| All, safely | "${a[@]}" |
| Count | "${#a[@]}" |
| Indexes / keys | "${!a[@]}" |
| Slice | "${a[@]:1:2}" |
| Delete element | unset 'a[1]' |
| From lines | mapfile -t a < file |
| Map | declare -A m; m[key]=val |
Related: Bash variables, for loops and functions.