Linux Basic — Indexed Array CRUD

3 minute read

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

Updated: