Skip to main content
5 of 7
Minor rephrasing
AdminBee
  • 23.6k
  • 25
  • 55
  • 77

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.txt with spaces in it, and want to move it to the directory playlists/.
  • If you store the filename in a variable $fname and call
    mv $fname playlists/
    
    the mv command will see four arguments: My, favorite, songs.txt and playlists/ and try to move the three nonexistant files My, favorite and songs.txt to the directory playlists/. Obviously not what you want.
  • Instead, if you place the $fname reference in double-quotes, as in
    mv "$fname" playlists/
    
    it makes sure the shell passes this entire string including the spaces as one word to 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.05 in 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 -t and 0.05 in an array variable and passing it as "${opt_array[@]}" the recipient will see this as two separate items, the -t and the 0.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 -u or date --utc in case of the date command. The way option values for an option (say, -o / --option) are interpreted by getopt is usually -ovalue or -o value for short options and --option=value or --option value for long options.
  • When passing -t 0.05 as two words to a tool that uses getopt(), it will take the first character after the - as being the option name and the next word as the option value (the -o value syntax). So, read would take t as option name and 0.05 as option value.
  • When passing -t 0.05 as one word, it will be interpreted as the -ovalue syntax: 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 be 0.05 with a leading space .
  • The read command apparently doesn't accept timeout specifications with a leading space. And indeed, if you call
    read -t " 0.05" -srn 1
    
    where the value is explicitly a string with leading space, read also 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.

AdminBee
  • 23.6k
  • 25
  • 55
  • 77