Bash mapfile builtin command
On Unix-like operating systems, mapfile is a builtin command of the Bash shell. It reads lines from standard input into an indexed array variable.
For more information about bash array variables, see arrays in bash.
Syntax
mapfile [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback [-c quantum]] [array]
Options
The mapfile builtin command takes the following options:
-n count | Read a maximum of count lines. If count is zero, all available lines are copied. |
-O origin | Begin writing lines to array array at index number origin. The default value is zero. |
-s count | Discard the first count lines before writing to array. |
-t | If any line ends in a newline, strip it. |
-u fd | Read lines from file descriptor fd rather than standard input. |
-C callback | Execute/evaluate a function/expression, callback, every time quantum lines are read. The default value of quantum is 1, unless otherwise specified with -c. |
-c quantum | Specify the number of lines, quantum, after which function/expression callback should be executed/evaluated if specified with -C. |
array | The name of the array variable where lines should be written. If array is omitted, the default variable MAPFILE is the target. |
Notes
The command name readarray may be used as an alias for the command name mapfile, with no difference in operation.
If the -u option is specified, mapfile reads from file descriptor fd instead of standard input.
If array is not specified, the default variable MAPFILE is used as the target array variable.
The mapfile command is not very portable. That is, if you want to ensure your script can run on a wide array of systems, it's not recommended to use mapfile. It's provided primarily as a convenience. The same functionality can be achieved using a read loop, although in general mapfile performs faster.
Exit status
The mapfile command returns 0 for success, or 1 if anything goes wrong, e.g., an invalid option is provided, or the target variable is read-only or not an array.
Examples
The mapfile command reads input line by line, and puts each line in an array variable. Let's provide it with multiple lines of input.
We can use printf to do this. It's an easy way to print text with newlines.
In our printf format string, we can include "\n" (a backslash immediately followed by a lowercase n) to create a newline. ("\n" is a metacharacter, a sequence of characters representing another character that can't be typed literally, such as the Enter key. For a complete list of bash metacharacters, see quoting in bash.)
This printf command prints three lines of text:
printf "Line 1\nLine 2\nLine 3\n"
Line 1 Line 2 Line 3
We want to use mapfile to put each of these lines in its own element of an array.
By default, mapfile reads from standard input, so you might be tempted to pipe the output of printf to mapfile like this:
printf "Line 1\nLine 2\nLine 3\n" | mapfile
You would expect the default array variable MAPFILE to contain the values from these lines. But if you check the value of MAPFILE:
echo "${MAPFILE[@]}"
[a blank line]
The variable is empty. Why?
Each command in a pipeline executes in a subshell — an instance of bash, executed as a child process. Each subshell has its own environment, and its own lexical scope — the variables that make up the environment of each subshell in the pipeline do not carry over to others. In other words, there are no environmental side effects shared from one element of a pipeline to the next. In our example above, mapfile works correctly, and sets the values of MAPFILE, but when the command's subshell terminates, the variable MAPFILE vanishes.
You can see this if you echo the value of MAPFILE inside a subshell that also contains the mapfile command, by enclosing both in parentheses:
printf "Line 1\nLine 2\nLine 3\n" | ( mapfile; echo "${MAPFILE[@]}" )
Line 1 Line 2 Line 3
In the above command, echo prints all the elements of array variable MAPFILE, separated by a space. The space appears at the beginning of lines 2 and 3 because of the newlines in our data. Our explicit subshell, expressed with parentheses, preserves the value of MAPFILE long enough for us to see the values.
We can fix the line breaks by stripping them with -t:
printf "Line 1\nLine 2\nLine 3\n" | ( mapfile -t; echo "${MAPFILE[@]}" )
Line 1 Line 2 Line 3
(We can put the line breaks back in our output if we use printf — we'll do that in subsequent examples.)
So, mapfile is working, but the array variable is inaccessible to the parent shell. Normally, however, you will want the MAPFILE variable to persist for subsequent commands. You can accomplish that with process substitution.
Using mapfile with process substitution
With process substitution, we can redirect output to mapfile without using a pipeline.
mapfile -t < <(printf "Line 1\nLine 2\nLine 3")
Let's look at the individual parts of this command:
mapfile -t | Mapfile takes input from standard input, and strip newlines (-t) from the end of each line. This is (usually) what you want: only the text of the line is stored in your array element, and the newline character is discarded. |
< | The first < is a redirection character. It expects to be followed by a file name, or file descriptor. The contents of that file are redirected to the standard input of the preceding command. |
<( ... ) | These characters indicate process substitution, which returns a file descriptor. The commands inside the parentheses are executed, and their output is assigned to this file descriptor. In any bash command, you can use process substitution like a file name. |
When you run the whole command, mapfile silently reads our three lines of text, and places each line into individual elements of the default array variable, MAPFILE.
We can verify this using printf to print the elements of the array.
printf "%s" "${MAPFILE[@]}"
The first argument, "%s" is the printf format string. The second argument, "${MAPFILE[@]}", is expanded by bash. All elements ("@") of array MAPFILE are expanded to individual arguments. (For more information, see: Referencing array elements in bash.)
Line 1Line 2Line 3
As you can see, our three lines of text are printed right next to each other. That's because we stripped the newlines with -t, and printf doesn't output newlines by default.
To specify that printf print a newline after each line, use \n in the format string:
printf "%s\n" "${MAPFILE[@]}"
Line 1 Line 2 Line 3
To access individual elements of the array, replace @ with an index number. The numbering is zero-based, so 0 indexes the first element, 1 indexes the second, etc:
printf "%s\n" "${MAPFILE[0]}"
Line 1
printf "%s\n" "${MAPFILE[2]}"
Line 3
Related commands
read — Read a line, split it into words, and assign each word to a variable.