Functions, arrays and associative arrays
Function definition and return versus output, local variables, positional parameters and shift, indexed arrays, mapfile, and associative arrays for lookups.
Functions
log() {
local level=$1; shift # local keeps the caller's variables intact
printf '%s [%s] %s
' "$(date -Is)" "$level" "$*" >&2
}
first_line() {
local file=$1
[[ -r $file ]] || return 1 # a status, not a message
head -n 1 -- "$file"
}
if value=$(first_line "$config"); then
log info "first line is $value"
else
log error "cannot read $config"
fi- Functions are called like commands and receive positional parameters
$1,$2,$@— there is no parameter list in the definition. returnsets an exit status between 0 and 255. Data comes back on stdout, which is why the example captures it with$(...).localis what makes functions safe to compose; without it every helper overwrites the caller's variables.- A function inside a subshell or behind a pipe cannot change the parent shell's state —
cdor variable changes are lost.
# keyword arguments are just a convention on top of positional ones
deploy() {
local env=staging tag=latest
while (( $# )); do
case "$1" in
--env) env=$2; shift 2 ;;
--tag) tag=$2; shift 2 ;;
--) shift; break ;;
*) break ;;
esac
done
printf 'deploy env=%s tag=%s targets=%s
' "$env" "$tag" "$*"
}Indexed arrays
servers=(web1 web2 web3)
servers+=(web4) # append one element
echo "${#servers[@]}" # element count
echo "${servers[0]}" # first element
echo "${servers[-1]}" # last element (bash 4.3+)
for s in "${servers[@]}"; do # quoted: each element stays one argument
printf '%s
' "$s"
done
printf '%s
' "${servers[@]}" # same, one element per line
mapfile -t lines < hosts.txt # file -> array, newline separated
mapfile -t -d '' paths < <(find . -type f -print0) # NUL separated, any filename
unset 'servers[1]' # leaves a gap; indices do not renumber
echo "${!servers[@]}" # which indices exist| Expression | Meaning |
|---|---|
arr=(a b c) | Create or replace the whole array |
arr+=(d) | Append without losing existing elements |
${arr[0]} | Single element; unquoted expansions still word-split |
${arr[@]} | Every element — always quote it |
${#arr[@]} | Number of elements |
${!arr[@]} | The list of valid indices |
${arr[@]:1:2} | Slice: two elements starting at index 1 |
"$@"behaves exactly like an array and is the right way to forward arguments:wrapper() { real_tool --flag "$@"; }.- Omitting the
[@](${arr}) silently returns element zero — a frequent and confusing bug.
Associative arrays
declare -A port # required; plain assignment creates an indexed array
port[web]=80
port[api]=8080
port[db]=5432
declare -A region=( # or all at once
[eu-west]=ireland
[us-east]=virginia
)
echo "${port[api]}"
for key in "${!region[@]}"; do
printf '%s -> %s
' "$key" "${region[$key]}"
done
[[ -v port[api] ]] && echo "api port is set" # bash 4.2+
unset 'port[db]'An associative array replaces the usual case ladder or the grep-the-config-file trick. Lookups are one operation and typo-safe compared with string comparison chains.
- Keys are arbitrary strings, so you do not need a naming convention like
CONFIG_EU_WEST. - Associative arrays need bash 4. The bash 3.2 that ships with macOS does not support them — that alone is a reason to check the target.
declare -Aon a name that is already an indexed array fails;unsetit first.
⚠️
Always create associative arrays with
declare -A. Without it map[key]=value is silently evaluated as an arithmetic index, so map[api]=8080 becomes element number 0 and looks like it worked until you read it back.FAQ
How do I return a value from a function?
Print it to stdout and capture with
$(fn); use return only for the status. Return two things at once as two lines, or set a variable whose name was passed in with printf -v.Why is my array empty after a function call?
An assignment such as
out=$(fn) runs the function in a subshell, so array changes do not survive. Have the function print one element per line and use mapfile -t arr < <(fn).Related
Parameter expansion and string manipulation Shells, startup files and running scripts
Last refreshed 2026-09-18.