| Tested on | RHEL 10.2 (Coughlan) |
|---|---|
| Package | bash 5.2.26-6.el10 |
| Applies to | Linux distributions with Bash installed |
| Privilege | Normal user; examples create files in the current directory |
| Scope | Bash mapfile / readarray: default MAPFILE, file input, -t, process substitution, -n, -s, -O, -d, NUL-delimited filenames with find -print0, -u, brief -C / -c, and comparison with while IFS= read -r. Does not cover general array CRUD, full process-substitution theory, or line-by-line streaming patterns. |
| Related guides | Bash while loop Shell scripting course hub |
mapfile reads lines or delimited records from standard input into a Bash indexed array. readarray is another name for the same builtin.
Use mapfile -t array < file for a text file, or mapfile -t array < <(command) for command output. The -t option removes the trailing record delimiter from each element.
mapfile fits inputs you can hold in memory at once. For very large files or unbounded streams, use while IFS= read -r instead so you process one line at a time rather than keeping the entire input in an array.
mapfile vs readarray
mapfile and readarray are two names for the same Bash builtin. Either form loads input records into an indexed array:
type mapfile readarraymapfile is a shell builtin
readarray is a shell builtinmapfile and readarray are Bash builtins available in Bash 4.0 and later; they are not POSIX shell commands — see Bash vs sh when a script must run under /bin/sh. The -d custom-delimiter option requires Bash 4.4 or later. If you see mapfile: command not found, check bash --version and make sure the script is actually running under Bash.
If you omit the array name, Bash stores the records in MAPFILE:
printf 'line1\nline2\nline3\n' > lines.txtClear any leftover MAPFILE entries, then load the file without -t so you can see the default newline handling:
unset MAPFILE
mapfile < lines.txt
declare -p MAPFILEdeclare -a MAPFILE=([0]=$'line1\n' [1]=$'line2\n' [2]=$'line3\n')Each element still ends with a newline character. Most scripts pass -t so elements hold the line text only.
Read a File into an Array
Redirect a file onto mapfile with <. The examples build a small config file first:
printf '%s\n' '# sample config' 'host=db1' 'port=5432' 'user=app' > app.confLoad every line into cfg and inspect the result:
mapfile -t cfg < app.conf
declare -p cfgdeclare -a cfg=([0]="# sample config" [1]="host=db1" [2]="port=5432" [3]="user=app")After the array exists, walk it with a Bash for loop when you need to process each setting:
for line in "${cfg[@]}"; do printf '%s\n' "$line"; done# sample config
host=db1
port=5432
user=appRemove Newlines with -t
The -t flag strips the record delimiter from the end of each element. With the default delimiter (newline), -t removes the trailing newline Bash would otherwise keep:
mapfile -t clean < lines.txt
declare -p cleandeclare -a clean=([0]="line1" [1]="line2" [2]="line3")Use -t on almost every line-oriented read. Without it, string comparisons and command arguments can silently include a hidden newline at the end of each element.
Read Command Output with Process Substitution
To load command output, feed process substitution to mapfile. This avoids first capturing the output as one command-substitution string and preserves each output line as a separate array element:
mapfile -t hosts < <(printf '%s\n' alpha beta gamma)
printf '%s\n' "${hosts[@]}"alpha
beta
gammaCommand substitution $(command) is a poor fit for record-safe loading: it removes trailing newlines from the captured text and collapses the stream into one string before mapfile sees it. Process substitution hands mapfile a file descriptor instead. When you already hold one delimited string and need to split it into elements, see split strings into arrays.
Read Only N Records with -n
-n count stops after count records. Keep the options before the array name:
printf '%s\n' 'name,role' 'alice,admin' 'bob,user' 'carol,user' > users.csvRead only the header row and the first data row:
mapfile -n 2 -t first2 < users.csv
declare -p first2declare -a first2=([0]="name,role" [1]="alice,admin")-n 0 means no limit — Bash reads until EOF.
Skip Records with -s
-s count discards the first count records before assignment. Skip the CSV header and load the remaining rows:
mapfile -s 1 -t body < users.csv
declare -p bodydeclare -a body=([0]="alice,admin" [1]="bob,user" [2]="carol,user")Combine -s and -n when you need a window into the middle of a file — for example, skip a header with -s 1 and cap how many data rows you pull with -n.
Append or Choose Starting Index with -O
Without -O, mapfile clears the target array before loading new records. With an explicit origin, existing elements outside the written range remain in place.
-O origin begins writing at array index origin instead of 0. Choose the next unused index when you want to append; choosing an existing index overwrites elements from that point:
existing=( keep )Load two more lines starting at index 1:
mapfile -O 1 -t existing < <(printf '%s\n' new1 new2)
declare -p existingdeclare -a existing=([0]="keep" [1]="new1" [2]="new2")Index 0 stays untouched while the new records fill 1 and 2.
Use a Custom Delimiter with -d
-d delim changes the record terminator from newline to the first character of delim. If delim is empty, Bash uses a NUL byte as the terminator. A value such as -d '::' still uses : only, not the two-character string ::. With a colon delimiter, each field between colons becomes one element:
mapfile -d ':' -t parts < <(printf '%s' 'alpha:beta:gamma')
declare -p partsdeclare -a parts=([0]="alpha" [1]="beta" [2]="gamma")Feed the builtin through process substitution or redirection — not a pipeline. With normal Bash pipeline behavior, putting mapfile on the right side of a pipe runs it in a subshell, so the resulting array is not available afterward. Prefer mapfile -t arr < <(command) instead.
Read Filenames Safely with NUL Delimiters
Filenames can contain spaces, tabs, or newlines. find -print0 ends each path with a NUL byte. Pair it with mapfile -d '' so each path is one array element:
mkdir -p 'dir with spaces'
printf 'ok\n' > 'dir with spaces/file one.txt'
printf 'ok\n' > 'dir with spaces/file two.txt'Load the NUL-delimited list:
mapfile -d '' -t found < <(find 'dir with spaces' -type f -print0 | sort -z)
printf '%q\n' "${found[@]}"dir\ with\ spaces/file\ one.txt
dir\ with\ spaces/file\ two.txtPipe through sort -z so NUL-delimited records stay intact and the sample output is deterministic — see the sort command for other ordering flags. Omit sort if ordering does not matter. %q shows the spaces in each path. A newline-delimited find listing would break on the first filename that contains a line break; NUL delimiters do not.
Read from a File Descriptor with -u
-u fd tells mapfile to read from that file descriptor instead of standard input. Open the file on descriptor 3, load it, then close the descriptor:
exec 3< app.confRead directly from descriptor 3:
mapfile -t -u 3 from3mapfile exits silently on success. Close the descriptor and confirm what was stored:
exec 3<&-
declare -p from3declare -a from3=([0]="# sample config" [1]="host=db1" [2]="port=5432" [3]="user=app")-u 3 tells mapfile to read from file descriptor 3 instead of standard input. Close the descriptor with exec 3<&- when finished.
Callback Processing with -C and -c
-C callback runs a callback periodically while mapfile is reading. Use -c quantum to choose how many records are read between callback invocations. If -C is used without -c, Bash uses a quantum of 5000.
Bash passes two additional arguments to the callback: the array index about to be assigned and the record that will be stored there. The callback runs after the record is read but before Bash assigns it. This is mainly useful for progress reporting or specialized processing; most scripts do not need -C or -c.
mapfile vs while IFS= read -r
| Concern | mapfile / readarray |
while IFS= read -r |
|---|---|---|
| Memory | Loads matching records into an array | One line in a variable at a time |
| Random access | "${arr[@]}", ${#arr[@]}, slices |
Forward-only unless you re-read |
| Large / unbounded input | Risky — whole batch in RAM | Preferred for logs and streams |
| Record handling | -d, -t, -n, -s operate on the whole load |
read handles one record per iteration and also supports options such as -d |
mapfile fits bounded inputs: config snippets, short command output, filename lists from find -print0. For reading a file line by line when the file may be huge or the pipeline never ends, stay with while IFS= read -r.
Common Mistakes
| Symptom | Likely cause | Fix |
|---|---|---|
Elements end with \n in comparisons |
Missing -t |
mapfile -t arr < file |
Array empty after command | mapfile |
Pipeline subshell | mapfile -t arr < <(command) |
Trailing blank lines disappear when using $(cmd) |
Command substitution removes trailing newlines | Feed command output with < <(cmd) |
-n or -s appears ignored |
Flags after the array name | Keep options before the array name |
| Words split across elements | Building a string with $(cmd) then parsing |
mapfile -t arr < <(cmd) |
mapfile loads whole log into RAM |
Unbounded input | Stream with while read |
Command substitution also merges the stream into one string before you assign it. Compare a single-line capture with a mapfile read:
one=$(printf 'solo\n')
printf 'cmdsub length=%s\n' "${#one}"cmdsub length=4mapfile still sees the line terminator as a record boundary:
mapfile -t soloarr < <(printf 'solo\n')
printf 'mapfile count=%s element=%q\n' "${#soloarr[@]}" "${soloarr[0]}"mapfile count=1 element=soloSummary
mapfile and readarray load delimited records from standard input or a file descriptor into an indexed array. Use file redirection for files and process substitution for command output. Use -t for normal line-oriented text, < file or < <(command) for input, and -n / -s when you need a slice of the stream. -O writes at a chosen starting index; -d '' with find -print0 handles awkward filenames safely.
mapfile keeps the whole batch in memory, which is convenient for config files and moderate command output but wrong for unbounded logs. When you only need a forward pass or the file is too large to hold in RAM, stream with while IFS= read -r instead. For creating, looping, and slicing arrays after they are loaded, see Bash arrays.
References
Frequently Asked Questions
Is readarray the same as mapfile?
Yes. They are two names for the same Bash builtin with the same options.
How do I remove trailing newlines?
Pass -t. It removes the record delimiter (newline by default) from the end of each element before assignment.
How do I read command output into an array?
Use process substitution: mapfile -t arr < <(your_command). Avoid mapfile -t arr <<< "$(your_command)" when trailing newlines or record boundaries matter.
Can mapfile handle filenames containing newlines?
Yes, when the input uses NUL delimiters. Run find … -print0 and load with mapfile -d '' -t files < <(find … -print0).
When should I use mapfile instead of while read?
Use mapfile when the input is bounded and you want every record in an array at once. Use while IFS= read -r to stream large or unbounded text with low memory use.

