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 $word1
, $word2
... $remaining_words
.
For instance on an input like:
<tab> foo bar\ baz bl\ah blah\
whatever whatever
and with the default value of $IFS
, read a b c
would assign:
$a
⇐ foo
$b
⇐ bar baz
$c
⇐ 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.
The -r
option removes the backslash processing. So that same command above with -r
would instead assign
$a
⇐ foo
$b
⇐ bar\
$c
⇐ 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.
With IFS=:
(:
being not an IFS whitespace character), an input like :foo::bar::
would be split into ""
, "foo"
, ""
, bar
and ""
(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 foo
and 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 (IFS=::
or 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 foo
, not foo:
.
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 zsh
).
Using var=value cmd
syntax makes sure IFS
is only set differently for the duration of that cmd
command.
History note
The 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 bar
to $b
, not the empty string.
In the Bourne shell, with:
var=value cmd
If cmd
is a built-in (like read
is), var
remains set to value
after 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:
line=`line`
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 yash
. ksh93
and 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)