Shell builtins · bash builtin
read
Read one line from standard input and split it into shell variables.
read [-ers] [-a ARRAY] [-d DELIM] [-n NCHARS] [-N NCHARS] [-p PROMPT] [-t TIMEOUT] [-u FD] [NAME...]
read takes one line from standard input, splits it into words using the characters in
IFS (space, tab and newline by default), and assigns the words to the names you give it in
order. The last name gets everything that is left, separators included. With no name, the
whole line lands in REPLY. It returns 0 when it read a line and non-zero at end of input,
which is what makes while read ...; do ...; done < file stop.
Two defaults surprise almost everyone. Backslashes are treated as escape characters and
removed unless you pass -r, and leading and trailing whitespace is trimmed unless IFS is
empty for that command. So the line-faithful form is IFS= read -r line, and it is what you
should type by habit.
read is a builtin because it has to set variables in the current shell. That is also its
biggest trap: cmd | read var runs read in a pipeline subshell in bash, and the variable
is gone on the next line. Feed it with <, a here-string <<< or process substitution
instead. For reading a whole file into an array, mapfile is shorter and faster.
Examples
Read a line into a variable
read name < data/hosts.txt
echo "first line: $name"
first line: web01 10.0.0.11
read consumes exactly one line, so only web01 10.0.0.11 is read. With a single
name, the whole line goes into it, internal spaces included.
Split a line into several variables
read host ip <<< "web01 10.0.0.11 primary rack-3"
echo "host=$host"
echo "ip=$ip"
host=web01 ip=10.0.0.11 primary rack-3
Words are assigned left to right and the last variable gets the rest of the line. That is
why ip holds 10.0.0.11 primary rack-3. Add a throwaway name such as _ at the end if
you want only the first fields.
Process a file line by line
while read -r host ip; do
echo "$host is at $ip"
done < data/hosts.txt
web01 is at 10.0.0.11 web02 is at 10.0.0.12 db01 is at 10.0.0.21 cache01 is at 10.0.0.31
The redirect after done feeds the whole loop. Each read takes the next line and the
loop ends when read hits end of file and returns non-zero.
Split on a different separator with IFS
tail -n +2 data/users.csv | head -n 3 | while IFS=, read -r id name email dept joined; do
printf '%-14s %s\n' "$name" "$dept"
done
Alice Moreau engineering Bob Okafor sales Chen Wei engineering
IFS=, set in front of read applies to that one command only. Names with spaces such
as Alice Moreau stay whole because only commas separate fields now. This is fine for
simple CSV; quoted fields with embedded commas need a real parser.
Keep backslashes with -r
printf '%s\n' 'C:\new\tools' > path.txt
read p < path.txt; echo "without -r: $p"
read -r p < path.txt; echo "with -r: $p"
without -r: C:newtools with -r: C:\new\tools
Without -r, a backslash escapes the next character and is removed, so \n becomes
n and \t becomes t. With -r the line arrives untouched. There is almost never a
reason to leave -r out.
Preserve leading spaces with IFS=
printf ' indented line \n' > in.txt
read -r line < in.txt; echo "[$line]"
IFS= read -r line < in.txt; echo "[$line]"
[indented line] [ indented line ]
With the default IFS, leading and trailing spaces are stripped. An empty IFS turns
splitting off, so the line is kept byte for byte. IFS= read -r line is the idiom for
reading lines exactly.
Do not lose a last line that has no newline
printf 'one\ntwo\nthree' > list.txt
while IFS= read -r l; do echo "got $l"; done < list.txt
echo ---
while IFS= read -r l || [ -n "$l" ]; do echo "got $l"; done < list.txt
got one got two --- got one got two got three
read stores three but returns non-zero because it hit end of file before a newline,
so the plain loop drops it. || [ -n "$l" ] runs the body once more when something was
read. Files written by some editors and by printf without \n end like this.
Read words into an array with -a
read -r -a parts <<< "deploy v2.4 to production"
echo "${#parts[@]} words, last is ${parts[-1]}"
echo "second: ${parts[1]}"
4 words, last is production second: v2.4
-a puts each word into consecutive array elements starting at index 0, so a line with
any number of fields can be handled. ${parts[-1]} is the last element.
Piping into read loses the variable
count=0
echo "a b c" | read -r first rest
echo "first=[$first]"
grep -c . todo.txt | read -r count
echo "count=$count"
first=[] count=0
Each part of a pipeline runs in a subshell in bash, so read sets its variables in a
process that exits right away. first is empty and count keeps its old 0. The
fixes are in the next example.
Read command output into variables in the current shell
read -r count < <(grep -c . todo.txt)
echo "count=$count"
read -r first rest <<< "a b c"
echo "first=$first rest=$rest"
count=5 first=a rest=b c
Process substitution < <(cmd) and the here-string <<< both feed read without a
pipeline, so it runs in the current shell and the variables survive. shopt -s
lastpipe is another option in scripts.
Read NUL-separated names with -d ''
find photos -name '*.png' -print0 | sort -z |
while IFS= read -r -d '' f; do
echo "file: $f"
done
file: photos/IMG_2041.png file: photos/beach.png file: photos/forest.png
-d '' makes NUL the line terminator, matching find -print0. Since NUL cannot occur in
a file name, this is the only fully safe way to loop over arbitrary names, including ones
with newlines.
Prompt for input with -p
read -r -p "Username: " user
echo "hello, $user"
hello, alice
-p prints a prompt (without a newline) on stderr, but only when input comes from a
terminal. Here stdin is fed from a pipe, so the prompt is not shown at all; the answer is
still read.
Give up after a timeout with -t
Setup run before this example
mkfifo slow{ sleep 3; echo late; } > slow &
if read -r -t 1 answer < slow; then echo "got $answer"; else echo "timed out, status $?"; fi
timed out, status 142
-t 1 waits at most one second. On timeout read returns a status greater than 128
(142 in bash) rather than 1, so a script can tell a timeout from end of file.
Read a fixed number of characters with -n
read -r -n 3 code <<< "ERR42 disk full"
echo "code=$code"
code=ERR
-n 3 returns after three characters without waiting for a newline. In a terminal this
is how "press y or n" prompts react to a single keypress (read -n 1).
Common pitfalls
Leaving out
-r: any backslash in the input is silently eaten. Writeread -revery time.cmd | while read ...; do count=$((count+1)); doneupdatescountin a subshell; after the loop it is unchanged. Usedone < <(cmd)instead.Reading from stdin inside a
while readloop: commands in the body such assshor anotherreadswallow the rest of the file. Give them< /dev/null(orssh -n), or read the loop from another descriptor withread -u 3 ... 3< file.Without
IFS=, leading and trailing whitespace is trimmed, which breaks indentation-sensitive files such as YAML or Makefiles.The last line of a file without a trailing newline is read but
readreturns 1, so the loop skips it. Add|| [ -n "$line" ].
GNU vs BSD and macOS
The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).
-a,-t,-n,-N,-s,-uand the<<<here-string are bash extensions that dash (/bin/shon Debian and Ubuntu) does not have; in a#!/bin/shscript stick toread -r. zsh'sreaduses-Afor arrays andread 'var?prompt'for prompts.
Options worth knowing
A selection, described in our own words. For the full list, see bash(1), SHELL BUILTIN COMMANDS.
| Option | What it does |
|---|---|
-r | Raw mode: backslashes are kept as ordinary characters. |
-a ARRAY | Assign the words to elements of ARRAY, starting at index 0. |
-d DELIM | Stop at the first character of DELIM instead of newline; an empty string means NUL. |
-p PROMPT | Print PROMPT on stderr before reading, only when input is a terminal. |
-t SECONDS | Fail with a status above 128 if no full line arrives in time; fractions are allowed. |
-n NCHARS | Return after NCHARS characters, or earlier at a delimiter. |
-N NCHARS | Read exactly NCHARS characters, ignoring delimiters and IFS splitting. |
-s | Silent: do not echo typed characters (for passwords). |
-u FD | Read from file descriptor FD instead of standard input. |
-e | Use readline for input when interactive, with line editing. |
-i TEXT | With -e, prefill the input line with TEXT. |
Related
mapfileRead lines from standard input into an indexed array, one line per element (also called readarray).printfPrint text and values through a format string, with precise control over spacing, numbers and escapes.echoPrint its arguments separated by spaces, followed by a newline.declareCreate variables with attributes (array, associative array, integer, read-only, exported), or show how variables are defined.- Reading files line by lineThe correct `while IFS= read -r line` loop part by part, the missing last line, the pipe-subshell trap, CSV fields, mapfile and stdin-stealing commands.
- Word splittingWhen bash splits expansion results into separate words, how IFS controls it, and why `for line in $(cat file)` does not loop over lines.
- Subshells and command groupingThe difference between ( ... ) and { ...; }, what a subshell inherits and what it cannot change, which constructs create one, and what that costs.
- Here-documents and here-stringsFeed multi-line text to a command with <<EOF, stop expansion with a quoted delimiter, indent with <<-, and pass one string with <<<.