Bash / Basic: syntax, values, and control / Lesson 6
Represent lists and keyed data with arrays
Use indexed and associative Bash arrays to preserve values, distinguish keys from positions, and avoid undocumented iteration assumptions.
On this page
Lesson goals
- Preserve empty and multiword array elements.
- Distinguish sparse indexes, element count, and keys.
- Use arrays for commands without evaluation.
An indexed array is not a split string
An indexed Bash array maps integer indexes to string values. Elements can include spaces, wildcard characters, and empty strings without encoding a separator. Assign values directly rather than reconstructing a list from printed output. "${items[@]}" expands each element separately; "${items[*]}" joins them using the applicable IFS character in the quoted case. These operations have different interfaces even when a simple display looks similar. Arrays are not POSIX sh syntax, so a script using them should declare Bash rather than silently hoping sh accepts the same constructs.
Element count does not imply contiguous indexes
Bash indexed arrays can be sparse. The number of assigned elements is not necessarily the highest index plus one, and looping from zero to count minus one can miss entries. Use "${!items[@]}" when indexes matter, or "${items[@]}" when only values matter. An array subscript is an arithmetic context, so external text must not be treated as a safe numeric key without validation. Distinguish an absent element from an existing empty value according to the supported expansion forms and options; test these cases rather than inferring them from printed blanks.
items=()
items[2]='red apple'
items[7]=''
printf 'count=%s\n' "${#items[@]}"
for index in "${!items[@]}"; do
printf '%s=<%s>\n' "$index" "${items[$index]}"
done
count=2
2=<red apple>
7=<>
There are two assigned elements at indexes two and seven. The empty value at seven still counts as an element and is preserved through quoted expansion.
Associative keys are a separate data model
declare -A creates an associative array in Bash versions that support it. Keys are nonempty strings and should be treated as data under the supported subscript semantics. Associative iteration order is not a stable presentation contract; print a declared key list or sort a deliberately encoded representation when order matters. Do not invent an order from one observed run. Avoid casual use of untrusted expressions in subscripts or indirect-variable machinery. Record the minimum feature requirements and test the deployed Bash, because array expansion behavior has received fixes and compatibility changes across releases.
Build an argument vector without eval
A command array can hold an executable or function name followed by exact arguments. Invoke it using "${command[@]}" instead of concatenating a shell-looking string. This preserves argument boundaries without a second parse. It does not validate the command name or the called utilityโs options, so those remain explicit policy decisions. printf also needs a fixed format: user data belongs in a %s argument, not in the format string. Debug rendering with %q can help a Bash reader inspect values, but its display is not a portable serialization protocol.
declare -A status=([draft]='needs review' [ready]='approved')
for key in draft ready; do
command=(printf '%s: %s\n' "$key" "${status[$key]}")
"${command[@]}"
done
draft: needs review
ready: approved
The fixed key list defines presentation order. The command array calls only printf with a fixed format and synthetic values, so no evaluation or filesystem lookup of data occurs.
Know when shell arrays stop being suitable
Bash arrays are convenient for moderate argument lists and small orchestration state, but they are not nested data structures, a binary store, or a database. Bash strings cannot preserve embedded NUL bytes. Very large lists can exhaust memory or exceed operating-system argument limits when passed to a child. Streaming a documented record format is often better than loading everything. Choose a language with appropriate data structures for complex parsing or high-volume computation. A maintainable Bash program keeps list representation explicit and avoids increasingly clever encodings that cannot preserve every legal value.
Common errors and fixes
Looping indexes from zero to element count
- Why it happens
- A sparse array was assumed contiguous.
- How to fix it
- Iterate actual indexes or values.
- Prevent it next time
- Include a sparse-array case in tests.
Relying on associative iteration order
- Why it happens
- An observed implementation order became an interface promise.
- How to fix it
- Use an explicit order or a suitable sorted representation.
- Prevent it next time
- Define output order independently of storage.
Joining a command array and evaluating it
- Why it happens
- A second parse discarded the original safe boundaries.
- How to fix it
- Invoke the quoted array directly.
- Prevent it next time
- Keep command selection and data arguments separate.
Check your understanding
1. An array has indexes 3 and 9. What is wrong with a loop over 0 and 1?
Show worked solution
It visits absent positions and misses both assigned elements. Iterate "${!items[@]}" for the actual indexes, quoting the expansion, or iterate values directly if the indexes have no semantic role.
2. How should a user-supplied percent sign be printed safely?
Show worked solution
Use printf with a fixed format such as printf '%s\n' "$value". The percent sign then remains data rather than becoming a formatting directive. This is separate from shell quoting and also applies to values stored in arrays.
Key takeaways
- Preserve list elements with quoted @ expansions.
- Keep ordering, command policy, and size limits explicit.