The reason is a difference in how the read builtin function and the date command interpret their command-line arguments.
The main reason why it is recommended to always quote your shell variables is to prevent unwanted word splitting. Consider the following
- You have a file called
My favorite songs.txtwith spaces in it, and want to move it to the directoryplaylists/. - If you store the filename in a variable
$fnameand call
themv $fname playlists/mvcommand will see four arguments:My,favorite,songs.txtandplaylists/and try to move the three nonexistant filesMy,favoriteandsongs.txtto the directoryplaylists/. Obviously not what you want. - Instead, if you place the
$fnamereference in double-quotes, as in
it makes sure the shell passes this entire string including the spaces as one word tomv "$fname" playlists/mv, so that it recognizes it is just one file (albeit with spaces in its name) that needs to be moved.
Now you have a situation in which you want to store option arguments in a shell variable. These are tricky, because sometimes they are long, sometimes short, and sometimes they take a value. There are numerous ways on how to specify options that take arguments, and usually how they are parsed is left entirely at the discretion of the programmer (see this Q&A) for a discussion). The reason why Bash's read builtin and the date command react differently is therefore likely in the internal workings on how these two parse their command-line arguments. However, we may speculate a little.
- When storing
-t 0.05in a scalar shell variable and passing it as"$opt_string", the recipient will see this as one string containing a space (see above). - When storing
-tand0.05in an array variable and passing it as"${opt_array[@]}"the recipient will see this as two separate items, the-tand the0.05.(1) - Many programs will use the
getopt()function from the GNU C library for parsing command-line arguments, as is recommended by the POSIX guidelines. - The
getopt()distinguishes "short" options and "long" option format, e.g.date -uordate --utcin case of thedatecommand. The way option values for an option (say,-o/--option) are interpreted bygetoptis usually-ovalueor-o valuefor short options and--option=valueor--option valuefor long options. - When passing
-t 0.05as two words to a tool that usesgetopt(), it will take the first character after the-as being the option name and the next word as the option value (the-o valuesyntax). So,readwould taketas option name and0.05as option value. - When passing
-t 0.05as one word, it will be interpreted as the-ovaluesyntax:getopt()will take (again) the first character after the-as the option name and the remainder of the string as option value, so the value would be0.05with a leading space . - The
readcommand apparently doesn't accept timeout specifications with a leading space. And indeed, if you call
where the value is explicitly a string with leading space,read -t " 0.05" -srn 1readalso complains about this.
As a conclusion, the date command is obviously written in a more lenient way when it comes to the option value for -d and doesn't care if the value string starts with a space. This is perhaps not unexpected, as the values that the date specifications can take on are very diverse, as opposed to the case of a timeout specification that (clearly) needs to be a number.
(1) Note that using the @ (as opposed to *) makes a great difference here, because when the array reference is quoted, all array elements will then appear as if they were individually quoted and thus could contain spaces themselves without being split further.