Read File Lines into Bash Arrays with mapfile
Use Bash mapfile to load file lines into an array without dropping the final line, plus version checks and memory limits.
18 Jun 2026, 11:23 UTC

The missing last line problem
You have a text file - say, a list of hostnames or deployment targets - and you need to process every line in a Bash script. A common approach is a while read loop:
while read -r line; do
echo "Processing $line"
done < targets.txt
This works until it does not. If the final line in targets.txt has no trailing newline, read returns a nonzero exit status and the loop body never runs for that line. The last entry is silently skipped. You can also lose variables set inside the loop if the pipeline runs in a subshell.
Takeaway: For indexed arrays in Bash 4.0 or later, mapfile (also called readarray) reads all lines into memory in one built-in call and keeps empty lines and the final unterminated line.
Check Bash version before you write the script
mapfile is a Bash built-in introduced in Bash 4.0. It does not exist in POSIX sh or Bash 3.x. Many macOS systems still ship Bash 3.2 as the default, so a script that works on Linux may fail with "command not found" on a Mac.
Run this in your terminal before depending on the feature:
bash --version
No special permissions are required. Look for a version number of 4.0 or higher. If you see version 3.2, install a newer Bash via Homebrew or use a portable while read pattern instead.
You can also confirm the built-in is available in your interactive shell:
type mapfile
help mapfile
Expected result: type reports mapfile is a shell builtin, and help lists options such as -t, -d, -n, -O, and -s.
Worked example: read a target list into an array
Suppose targets.txt contains one hostname per line, possibly with a blank line and no newline on the final entry:
web-01.example.com
web-03.example.com
Run the following in the directory that contains the file. You need read permission on targets.txt; no elevated privileges are required.
#!/usr/bin/env bash
# Fail if Bash is too old for mapfile
if (( BASH_VERSINFO[0] < 4 )); then
echo "This script requires Bash 4.0 or later" >&2
exit 1
fi
mapfile -t targets < targets.txt
echo "Read ${#targets[@]} lines"
for target in "${targets[@]}"; do
[[ -z "$target" ]] && continue
echo "Checking $target"
done
Placeholders: replace targets.txt with your input file path. The -t flag strips the trailing delimiter (newline) from each element, so target does not carry an invisible line break into commands like ssh or curl.
Expected checks:
${#targets[@]}reports the element count, including empty lines.- Empty elements are skipped by the
[[ -z "$target" ]]guard. - The final unterminated line is still captured.
Risk: without -t, every element keeps its newline character. Downstream commands may behave unexpectedly because the variable value ends with \n.
Trade-offs and limits
mapfile loads the entire file into RAM as an array. For a small configuration or host list this is fine. For multi-gigabyte logs, streaming with while read -r uses far less memory because it processes one line at a time.
Portability is the bigger constraint. Because macOS defaults to Bash 3.2, do not use mapfile in scripts you distribute to macOS users unless you require them to install Bash 5 or use the shebang #!/usr/local/bin/bash after a Homebrew install. Always pair the shebang with a runtime version check like the one above.
Closing checklist
- Run
bash --versionand confirm Bash 4.0 or later. - Use
mapfile -t array < fileto strip trailing newlines. - Quote array expansion as
"${array[@]}"to preserve elements with spaces. - Prefer streaming
while readfor very large files or Bash 3.x hosts.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.