Chapter 07Lesson 04~80 minutes

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.

BeginnerArrays & dataHands-on lab

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)
Record contract

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.txt
Memory tradeoff

Use 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"
fi

11. 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?

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.
Next lesson

When Bash Data Structures Are the Wrong Abstraction

Continue Chapter 7 by building a safer model for structured shell state.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.