bill.lee
bill.lee

Reputation: 2375

`read` builtin does not preserve input that is whitespace only

I am trying to read a keystroke input from the keyboard and assign it to a variable key. I am using read as follows:

read -sn1 key && echo "${#key}" "\"$key\"" "${key:?}"

This reads a single character (aka keystroke) from the keyboard and sets key to that character and echos info about key. If I type a readable character (d), it will show, for example:

1 "d" d

But if I type a tab or space, I get (from the last part of the echo statement):

bash: key: parameter null or not set

However, if I use the default REPLY and hit the space bar, it works as expected when I hit the space bar:

$ read -sn1 && echo "${#REPLY}" "\"$REPLY\"" "${REPLY:?}"
1 " "

Also, if I explicitly specify REPLY as the name of the variable to use, explicitly, it does not work as the last example showed, instead, it fails as when I used key.

$ read -sn1 REPLY && echo "${#REPLY}" "\"$REPLY\""
0 ""

What's going on? It seems like specifying a name to read to store the results to cause it to lose whitespace-only input. (I verified this by removing -n and entering longer whitespace-only input).

Upvotes: 1

Views: 175

Answers (2)

chepner
chepner

Reputation: 531165

read performs word-splitting on the line it reads in order to set the variables provided as its arguments. As part of word-splitting, any leading or trailing whitespace is discarded; other whitespace is retained if splitting isn't required to produce the requested number of fields. For example,

$ read a b <<< " foo bar      baz  "
$ echo "=$a="
=foo=
$ echo "=$b="
=bar      baz=

Because only two fields are requested, the whitespace between bar and baz is left alone.

Wordsplitting occurs even if only one name is used.

$ read a <<< "   foo       bar    "
$ echo "=$a="
=foo       bar=

There are two ways to disable word-splitting:

  1. Unset or clear the value of IFS. This retains all leading/trailing whitespace and assigns the entire line to the first name given as an argument.
  2. A bash extension uses the name REPLY if no name arguments are provided to read; word-splitting is not performed before assigning the input to REPLY.

Upvotes: 2

that other guy
that other guy

Reputation: 123470

Here's help read:

The line is split into fields as with word splitting, and the first word is assigned to the first NAME, the second word to the second NAME, and so on, with any leftover words assigned to the last NAME. Only the characters found in $IFS are recognized as word delimiters.

If no NAMEs are supplied, the line read is stored in the REPLY variable.

You'll notice that this means that read is not the same as read REPLY.

When you specify a name argument, word splitting will occur (even if it's just one). If you don't specify a name, the line is simply stored in REPLY as-is, i.e. without any word splitting. This is how the single space survives.

Upvotes: 4

Related Questions