In POSIX shells,
read, without any option doesn't read a line, it reads words from a (possibly backslash-continued) line, where words are
$IFS delimited and backslash can be used to escape the delimiters (or continue lines).
The generic syntax is:
read word1 word2... remaining_words
read reads stdin one byte at a time¹ until it finds an unescaped newline character (or end-of-input), splits that according to complex rules and stores the result of that splitting into
For instance on an input like:
<tab> foo bar\ baz bl\ah blah\
and with the default value of
read a b c would assign:
blah blahwhatever whatever
Now if passed only one argument, that doesn't become
read line. It's still
read remaining_words. Backslash processing is still done, IFS whitespace characters² are still removed from the beginning and end.
-r option removes the backslash processing. So that same command above with
-r would instead assign
baz bl\ah blah\
Now, for the splitting part, it's important to realise that there are two classes of characters for
$IFS: the IFS whitespace characters² (including space and tab (and newline, though here that doesn't matter unless you use -d), which also happen to be in the default value of
$IFS) and the others. The treatment for those two classes of characters is different.
: being not an IFS whitespace character), an input like
:foo::bar:: would be split into
"" (and an extra
"" with some implementations though that doesn't matter except for
read -a). While if we replace that
: with space, the splitting is done into only
bar. That is leading and trailing ones are ignored, and sequences of them are treated like one. There are additional rules when whitespace and non-whitespace characters are combined in
$IFS. Some implementations can add/remove the special treatment by doubling the characters in IFS (
So here, if we don't want the leading and trailing unescaped whitespace characters to be stripped, we need to remove those IFS white space characters from IFS.
Even with IFS-non-whitespace characters, if the input line contains one (and only one) of those characters and it's the last character in the line (like
IFS=: read -r word on a input like
foo:) with POSIX shells (not
zsh nor some
pdksh versions), that input is considered as one
foo word because in those shells, the characters
$IFS are considered as terminators, so
word will contain
So, the canonical way to read one line of input with the
read builtin is:
IFS= read -r line
(note that for most
read implementations, that only works for text lines as the NUL character is not supported except in
var=value cmd syntax makes sure
IFS is only set differently for the duration of that
read builtin was introduced by the Bourne shell and was already to read words, not lines. There are a few important differences with modern POSIX shells.
The Bourne shell's
read didn't support a
-r option (which was introduced by the Korn shell), so there's no way to disable backslash processing other than pre-processing the input with something like
sed 's/\\/&&/g' there.
The Bourne shell didn't have that notion of two classes of characters (which again was introduced by ksh). In the Bourne shell all characters undergo the same treatment as IFS whitespace characters do in ksh, that is
IFS=: read a b c on an input like
foo::bar would assign
$b, not the empty string.
In the Bourne shell, with:
cmd is a built-in (like
var remains set to
cmd has finished. That's particularly critical with
$IFS because in the Bourne shell,
$IFS is used to split everything, not only the expansions. Also, if you remove the space character from
$IFS in the Bourne shell,
"$@" no longer works.
In the Bourne shell, redirecting a compound command causes it to run in a subshell (in the earliest versions, even things like
read var < file or
exec 3< file; read var <&3 didn't work), so it was rare in the Bourne shell to use
read for anything but user input on the terminal (where that line continuation handling made sense)
Some Unices (like HP/UX, there's also one in
util-linux) still have a
line command to read one line of input (that used to be a standard UNIX command up until the Single UNIX Specification version 2).
That's basically the same as
head -n 1 except that it reads one byte at a time to make sure it doesn't read more than one line. On those systems, you can do:
Of course, that means spawning a new process, execute a command and read its output through a pipe, so a lot less efficient than ksh's
IFS= read -r line, but still a lot more intuitive.
¹ though on seekable input, some implementations can revert to reading by blocks and seek-back afterwards as an optimisation. ksh93 goes even further and remembers what was read and uses it for the next
read invocation, though that's currently broken
² IFS whitespace characters, per POSIX being the characters classified as
[:space:] in the locale and that happen to be in
$IFS though in ksh88 (on which the POSIX specification is based) and in most shells, that's still limited to SPC, TAB and NL. The only POSIX compliant shell in that regard I found was
bash (since 5.0) also include other whitespace (such as CR, FF, VT...), but limited to the single-byte ones (beware on some systems like Solaris, that includes the non-breaking-space which is single byte in some locales)