Linux Basic — Indexed Array CRUD
Indexed Array CRUD
Indexed arrays in Bash use non-negative integer keys and preserve insertion order. They’re the right choice whenever you need an ordered list of values — filenames, command-line arguments, log entries, anything.
Create
Four ways to make one:
# Literal
fruits=(apple banana cherry)
# Empty, then assign
declare -a fruits # 'declare -a' is optional but documents intent
fruits[0]=apple
fruits[1]=banana
# From a glob
images=( *.png )
# From command output (one element per line)
mapfile -t lines < <(some_cmd)
mapfile (aka readarray) is the right way to slurp command output into an array. Avoid arr=( $(cmd) ) — it splits on whitespace and breaks on filenames with spaces.
Read
fruits=(apple banana cherry)
echo "${fruits[0]}" # apple
echo "${fruits[2]}" # cherry
echo "${fruits[-1]}" # cherry (last; Bash 4.3+)
echo "${fruits[-2]}" # banana
# All elements as separate words (the safe way)
for f in "${fruits[@]}"; do
echo "[$f]"
done
# Count
echo "${#fruits[@]}" # 3
# Indexes (sparse-aware — see below)
echo "${!fruits[@]}" # 0 1 2
"${arr[@]}" — always quote it, always use @, when you want each element as a separate word.
Append
fruits=(apple)
fruits+=(banana) # append one
fruits+=(cherry date) # append multiple
printf '%s\n' "${fruits[@]}"
# apple
# banana
# cherry
# date
+=() is the cleanest. The older form fruits[${#fruits[@]}]=banana works but is verbose and wrong for sparse arrays.
Update
fruits=(apple banana cherry)
fruits[1]=blueberry
echo "${fruits[@]}"
# apple blueberry cherry
# Out-of-range index → grows the array, leaves a hole
fruits[10]=elderberry
echo "${#fruits[@]}" # 4 (count, not max-index)
echo "${!fruits[@]}" # 0 1 2 10
That gap matters — Bash arrays are sparse. If you unset an element in the middle, the surrounding indexes don’t shift.
Delete — unset vs Reassign
fruits=(apple banana cherry date)
# Delete one element
unset 'fruits[1]'
echo "${fruits[@]}" # apple cherry date
echo "${!fruits[@]}" # 0 2 3 ← hole at index 1!
echo "${#fruits[@]}" # 3 (count of present elements)
# Re-pack (no holes)
fruits=( "${fruits[@]}" )
echo "${!fruits[@]}" # 0 1 2
Always quote the unset target: unset 'fruits[1]'. Without quotes, the [1] can trigger glob expansion if a file fruits1 exists in cwd.
To clear the whole array:
fruits=() # preferred
unset fruits # also works; removes the variable entirely
Iterate Two Ways
fruits=(apple banana cherry)
# By value (most common)
for f in "${fruits[@]}"; do
echo "$f"
done
# By index (when you also need the position)
for i in "${!fruits[@]}"; do
echo "$i: ${fruits[$i]}"
done
# 0: apple
# 1: banana
# 2: cherry
Iterating by index is the right choice when:
- The array might be sparse (indexes 0, 2, 5 —
for f in "${arr[@]}"skips the holes by value, but doesn’t tell you the position) - You need to modify the element in place
Length & Element Length
arr=(one two three)
echo "${#arr[@]}" # 3 count
echo "${#arr[2]}" # 5 byte-length of arr[2] = "three"
Same ${#…} operator from string length — applied to [@] it returns the element count, applied to [i] it returns that element’s byte length.
Check If An Index Exists
arr=(a b c)
unset 'arr[1]'
# Wrong — `-n "${arr[1]}"` is also true if the value is non-empty
# but false for empty-string OR missing — we can't tell them apart
[[ -n "${arr[1]}" ]] && echo "exists"
# Right — use `-v` for "is set" (Bash 4.2+)
[[ -v arr[1] ]] && echo "exists" || echo "missing"
# missing
-v is the only correct way to ask “is this index set?” — it distinguishes “set to empty string” from “not set at all.”
A Few Useful Idioms
# Pop the last element
last=${arr[-1]}
unset 'arr[-1]'
# Push to front (a real allocation, no native prepend)
arr=(new "${arr[@]}")
# Reverse (Bash 4+)
rev=()
for ((i=${#arr[@]}-1; i>=0; i--)); do
rev+=("${arr[i]}")
done
# Remove all empty strings
clean=()
for x in "${arr[@]}"; do
[[ -n "$x" ]] && clean+=("$x")
done
For deduplication and sorting, see the Sorting Arrays page.
Common Mistakes
| ❌ Wrong | ✅ Right | Issue |
|---|---|---|
for f in ${arr[@]} (unquoted) |
for f in "${arr[@]}" |
unquoted splits on $IFS, breaks on spaces |
arr=( $(cmd) ) |
mapfile -t arr < <(cmd) |
command substitution splits on whitespace |
unset arr[1] (unquoted) |
unset 'arr[1]' |
bracket-glob expansion risk |
[[ -n "${arr[1]}" ]] to check “is set” |
[[ -v arr[1] ]] |
doesn’t distinguish empty from missing |
Iterating with for i in {0..${#arr[@]}} |
use for i in "${!arr[@]}" |
brace expansion happens before $ |
Next → Associative Array CRUD