skip to content

Indexed Arrays

The only safe way to hold a list of things that may contain spaces. The heart of it is one rule — always expand as "${arr[@]}" — plus the indices, slicing, and append syntax that make arrays usable for building command lines.

part ofBashoverview, primer and where to startread it →
on this pageshow

questions

5

In bash, how do you create an indexed array, append to it, read a single element and get the number of elements — and why does `echo $arr` print only the first element?

level: juniorimportance: must knowfreq 70%

answer

  1. parentheses make a list
  2. bare name means subscript zero
  3. braces are required around subscripts
  4. count versus character length

basics

~10 s

Create with arr=(a b c), append with arr+=(d), read one element as "${arr[0]}", count with "${#arr[@]}". A bare $arr is shorthand for ${arr[0]}, so it shows only the first element.

solid answer

~40 s

An indexed array literal is a parenthesised word list: `arr=(alpha "beta gamma" delta)`. Each word becomes one element, and quoting keeps a multi-word value in a single element. `arr+=(epsilon)` appends after the highest index. You read one element with `${arr[0]}` — the braces are mandatory, because `$arr[0]` expands `$arr` and then leaves a literal `[0]`. `${#arr[@]}` is the element count, while `${#arr}` is the character length of element 0, which is a classic trip-up. `${!arr[@]}` gives the indices, and `${arr[@]:1:2}` takes a two-element slice. Bare `$arr` is defined to mean `${arr[0]}`, so it never prints the whole array; to get every element, expand `"${arr[@]}"`. `declare -p arr` prints the real structure when you are unsure what you have.

code

bash · 9 lines
bash
arr=(alpha "beta gamma" delta)
echo "${arr[0]}"        # alpha
echo "${arr[1]}"        # beta gamma
echo "${#arr[@]}"       # 3  (elements)
echo "${#arr}"          # 5  (characters in alpha)
echo "${#arr[1]}"       # 10 (characters in beta gamma)
arr+=(epsilon)
printf '%s\n' "${arr[@]:1:2}"   # beta gamma / delta
declare -p arr

go deeper

for a junior

Say the four basics without hesitating: arr=(a b c) creates, arr+=(d) appends, "${arr[0]}" reads, "${#arr[@]}" counts. Add that a bare $arr is only the first element.

for a middle

Explain why the braces are mandatory — the subscript is part of the expansion syntax, not of the name — and distinguish ${#arr} (characters in element 0) from ${#arr[@]} (element count).

for a senior

Show the habits that survive real data: quote on the way in and on the way out, use declare -p to inspect element boundaries, and know that an empty array expands to zero words rather than one empty argument.

for a principal

Be ready to say where the shell's single flat list type runs out. When a script needs records, nested structures or keyed lookups threaded through many functions, argue for moving that logic to a real language rather than encoding structure into array elements.

## What an indexed array is An indexed array in bash is a variable that holds many values, each stored under an integer subscript starting at 0. It is the only built-in way to hold a list whose items may contain spaces, tabs or newlines: a plain string variable holding `a b c` is one value that only *looks* like three, and the shell will happily re-split it in ways you did not intend. ## Creating one The compound assignment is a parenthesised list of words: ```bash arr=(alpha "beta gamma" delta) ``` The words inside the parentheses go through the normal expansions, so quoting matters exactly as it does on a command line. `"beta gamma"` is one element; without the quotes it would be two. That gives three elements at indices 0, 1 and 2. You can also assign a single slot directly, `arr[7]=eta`, and bash will create the array for you. `declare -a arr` declares an empty indexed array explicitly, which is useful when a later `+=` is the first thing that touches it. ## Reading one element ```bash echo "${arr[1]}" # beta gamma ``` The braces are not optional. In `$arr[0]`, bash sees the parameter name `arr`, expands it, and leaves `[0]` as literal text, so you get `alpha[0]`. Any time a subscript is involved, the whole thing goes inside `${ }`. The subscript itself is an arithmetic context, so `${arr[i]}` works with a bare variable name and `${arr[n+1]}` does arithmetic. Bash 4.3 and later also accept negative subscripts, where `${arr[-1]}` is the last element. ## Length, count and indices Two expansions look almost identical and mean different things: ```bash echo "${#arr[@]}" # 3 -> number of elements echo "${#arr}" # 5 -> characters in ${arr[0]}, i.e. "alpha" echo "${#arr[1]}" # 10 -> characters in "beta gamma" ``` `${!arr[@]}` lists the subscripts that actually exist, which is the reliable way to iterate when the array has gaps. `${arr[@]:1:2}` is a slice of two elements starting at offset 1; `${arr[@]:1}` takes everything from offset 1 onward. ## Appending ```bash arr+=(epsilon zeta) ``` The `+=` compound assignment appends after the highest existing index — it does not overwrite and it does not care that earlier indices may be missing. This is the idiom for building a list in a loop, one `arr+=("$item")` per iteration, with the item quoted so a value containing spaces stays a single element. ## Why bare $arr is the first element Bash defines a bare array name without a subscript as equivalent to subscript 0. So `echo $arr` prints `alpha` and `arr=x` silently overwrites element 0 while leaving the rest of the array in place. There is no syntax that turns the whole array into a value except `${arr[@]}` and `${arr[*]}`, and in practice you almost always want the quoted `"${arr[@]}"`, which produces one word per element with the contents untouched. The same asymmetry explains why `${#arr}` counts characters: with no subscript, bash is measuring the string in element 0, exactly as it would for any ordinary variable. ## Inspecting what you actually have When an array is behaving oddly, print its real structure instead of guessing: ```bash declare -p arr # declare -a arr=([0]="alpha" [1]="beta gamma" [2]="delta") ``` This shows the element boundaries and the actual subscripts, which is far more informative than `echo`, where the boundaries are invisible. `printf '[%s]\n' "${arr[@]}"` is the quick equivalent: one bracketed line per element makes a stray empty element or an accidentally-merged pair obvious. ## Practical habits Quote on assignment (`arr+=("$line")`), quote on expansion (`"${arr[@]}"`), use `${#arr[@]}` for counts, and reach for `declare -p` when debugging. Those four habits cover nearly everything a script does with a list.

  • What does `arr=x` do to an array that already has three elements?
    It assigns to element 0 only, because a bare array name means subscript 0. Elements 1 and 2 survive untouched, so the array silently ends up with a new first value and the old tail. If you meant to replace the whole thing, use `arr=(x)`, or clear it first with `unset arr`.
  • How do you iterate over an array together with its indices?
    Loop over `"${!arr[@]}"`, which expands to the subscripts that exist, and use each one to read the value: `for i in "${!arr[@]}"; do printf '%s=%s\n' "$i" "${arr[i]}"; done`. This is correct even if some subscripts are missing, whereas a counter running from 0 to `${#arr[@]}-1` is not.
  • What does `${arr[@]:1:2}` give you, and what happens if you ask for more elements than exist?
    It is a slice: up to two elements starting at offset 1. Asking for more than exist is not an error — you simply get however many are available, and an offset past the end yields nothing at all. A leading space is needed for a negative offset, `${arr[@]: -2}`, otherwise bash reads `:-` as the default-value operator.

saying these in an interview costs you the question

  • Thinking $arr expands to the whole array
  • Using ${#arr} to count elements
  • Writing $arr[0] without braces
  • Assuming arr+=x appends a new element
  • Believing an array element cannot contain spaces

context

open as a page

In bash, what is the difference between expanding an array as "${arr[@]}" and as "${arr[*]}", and what changes if you drop the double quotes from either one?

level: middleimportance: must knowfreq 62%

basics

~20 s

Quoted "${arr[@]}" produces one word per element with contents preserved. Quoted "${arr[*]}" produces a single word, elements joined by the first character of IFS. Unquoted, both collapse to one string that is then word-split and glob-expanded.

open as a page

A script builds options in a string, `OPTS="--output 'my report.csv'"`, then runs `mytool $OPTS`. The tool receives four arguments instead of two, and one of them starts with a quote character. Why, and how should the option list be built instead?

level: middleimportance: should knowfreq 50%

basics

~20 s

Word splitting happens after the variable expands, and the quotes inside the string are then just literal characters — bash never re-parses them. Store each argument as its own array element and run mytool "${opts[@]}".

open as a page

In bash, why is `files=($(find . -name '*.log'))` an unsafe way to capture a list of filenames into an array, and what would you use instead?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The unquoted substitution is split on IFS and then glob-expanded, so any filename containing a space, tab or newline becomes several elements and one containing a glob character can be replaced by other names. Fill the array with mapfile -t, or a NUL-delimited read loop.

open as a page

In bash, after `arr=(a b c); unset 'arr[1]'`, what do `${#arr[@]}` and `${!arr[@]}` report, and why is a C-style loop from 0 to `${#arr[@]}-1` the wrong way to iterate an array?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

Bash arrays are sparse: unset removes index 1 without renumbering, so ${#arr[@]} is 2 while the live indices from ${!arr[@]} are 0 and 2. A counter loop bounded by the count would read index 1, which no longer exists, and never reach index 2.

open as a page