Reading Command Output into Arrays with mapfile
Sometimes a command produces a finite list that you genuinely want in memory. `mapfile`—also available as `readarray`—reads records directly into a Bash array without passing the data through unquoted command substitution and word splitting.
Learning objectives
By the end of this lesson
- Read lines into arrays with mapfile.
- Use process substitution to capture command output.
- Understand delimiter and newline behavior.
- Recognize memory and streaming tradeoffs.
- Avoid unsafe array creation from $(command).
1. mapfile reads lines into an indexed array
printf '%s\n' api worker cache > services.txt
mapfile -t services < services.txt
printf 'count=%d\n' "${#services[@]}"
printf '<%s>\n' "${services[@]}"The -t option removes each line delimiter from the stored element. Without it, newline characters are retained.
2. readarray is a synonym
readarray -t services < services.txt
printf '%s\n' "${services[@]}"Use whichever name your team finds clearer; both refer to the Bash builtin.
3. Process substitution connects command output without a pipeline subshell
mapfile -t services < <(
printf '%s\n' "api gateway" worker cache
)
printf 'count=%d\n' "${#services[@]}"
printf '<%s>\n' "${services[@]}"Each output line becomes one element, so embedded spaces remain intact.
4. Do not populate arrays with unquoted command substitution
# Fragile:
# services=( $(some_command) )
# Safe for line-oriented output:
mapfile -t services < <(some_command)Using mapfile is safe only when the producer's record delimiter matches your intended array elements. Lines are not a universal structured-data format.
5. mapfile can use a custom delimiter
printf 'api\0worker\0cache\0' > services.bin
mapfile -d '' -t services < services.bin
printf '<%s>\n' "${services[@]}"A NUL delimiter is useful for filename-safe records when paired with tools that produce NUL-separated output.
6. Read arbitrary filenames into an array
mapfile -d '' -t files < <(
find ./artifacts -type f -print0
)
printf 'found=%d\n' "${#files[@]}"
printf 'path=%q\n' "${files[@]}"This keeps spaces, tabs, and embedded newlines inside filenames from being misinterpreted as record separators.
7. -O appends starting at a chosen index
services=(api worker)
mapfile -t -O "${#services[@]}" services < <(
printf '%s\n' cache metrics
)
printf '%s\n' "${services[@]}"This is useful when building one array from several finite sources.
8. -s and -n can skip and limit input
printf '%s\n' a b c d e > values.txt
mapfile -t -s 1 -n 3 selected < values.txt
printf '%s\n' "${selected[@]}"Here one record is skipped and at most three records are stored.
9. mapfile loads data into memory
For large or unbounded streams, a while read loop is usually better because it processes records incrementally.
while IFS= read -r line; do
process_record "$line"
done < huge-input.txtUse mapfile when the finite collection genuinely needs random access or repeated use. Stream when one-pass processing is enough.
10. Producer failure needs explicit thought
Process substitution makes data capture convenient, but failure of the producer inside < <(...) is not represented as simply as a normal pipeline status.
# If source generation must succeed before proceeding,
# consider a validated temporary file:
tmp=$(mktemp)
if generate_inventory >"$tmp"; then
mapfile -t items <"$tmp"
rm -f "$tmp"
else
status=$?
rm -f "$tmp"
exit "$status"
fi11. Hands-on lab: load service inventory
mkdir -p "$HOME/devops-academy/bash/chapter07/lesson04"
cd "$HOME/devops-academy/bash/chapter07/lesson04"
inventory() {
printf '%s\n' \
"api gateway" \
"worker" \
"cache" \
"metrics"
}
mapfile -t services < <(inventory)
printf 'services=%d\n' "${#services[@]}"
for service in "${services[@]}"; do
printf 'service=<%s>\n' "$service"
done
mapfile -t -O "${#services[@]}" services < <(
printf '%s\n' scheduler
)
printf 'after append=%d\n' "${#services[@]}"Verification checklist
12. Knowledge check
Question 1. What does mapfile -t do?
Question 2. Why use process substitution with mapfile?
Question 3. When is while read preferable?
Question 4. How do you safely ingest arbitrary filenames?
find -print0 and mapfile -d ''.13. Summary
mapfile converts finite record streams directly into Bash arrays. It avoids command-substitution splitting, supports custom delimiters, and pairs well with process substitution. Use streaming instead when data is large or one-pass processing is sufficient.
14. Further reading
- GNU Bash Reference Manual —
mapfile. - GNU Bash Reference Manual — Process Substitution.
- GNU findutils manual —
-print0. - ShellCheck documentation — command substitution and arrays.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this address.