Why bash “read -t 0” does not see input?

My script (should) acts differently, depending on the presence of the data in the input stream. So I can invoke it like this:

$ my-script.sh

or:

$ my-script.sh <<-MARK
    Data comes...
    ...data goes.
MARK

or:

$ some-command | my-script.sh

where two last cases should read the data, while first case should notice the data is missing and act accordingly.

The crucial part (excerpt) of the script is:

#!/bin/bash
local myData;
read -d '' -t 0 myData;

[ -z "${myData}" ] && {
    # Notice the lack of the data.
} || {
    # Process the data.
}

I use read to read input data, then option -d '' to read multiple lines, as this is expected, and the -t 0 to set timeout to zero. Why the timeout? According to help read (typing left unchanged; bold is mine):

-t timeout time out and return failure if a complete line of input is
not read withint TIMEOUT seconds. The value of the TMOUT
variable is the default timeout. TIMEOUT may be a
fractional number. If TIMEOUT is 0, read returns success only
if input is available on the specified file descriptor
. The
exit status is greater than 128 if the timeout is exceeded

So I in case 2 and 3 it should read the data immediately, as I understand it. Unfortunately it doesn’t. As -t can take fractional values (according to above man page), changing read line to:

read -d '' -t 0.01 myData;

actually reads the data when data is present and skips it (after 10ms timeout) if it is not. But it should also work when TIMEOUT is set to real 0.

Why it actually doesn’t? How can this be fixed? And is there, perhaps, alternative solution to the problem of “act differently depending on the presence of the data”?

UPDATE

Thanks to @Isaac I found a misleading discrepancy between quoted on-line version and my local one (normally I do not have locale set to en_US, so help read gave me translation which I couldn’t paste here, and looking up for on-line translation was faster than setting new env—but that caused the whole problem).

So for 4.4.12 version of Bash it says:

If TIMEOUT is 0, read returns
immediately, without trying to read any data, returning
success only if input is available on the specified
file descriptor.

This gives a little bit different impression than “If TIMEOUT is 0, read returns success only if input is available on the specified file descriptor”—for me it implied actually an attempt to read the data.

So finally I tested this and it worked perfectly:

read -t 0 && read -d '' myData;

The meaning: see if there’s anything to read and if it succeed, just read it.

So as to base question, the correct answer was provided by Isaac. And as to alternative solution I prefer the above “read && read” method.

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

No, read -t 0 will not read any data.

You are reading the wrong manual. The man read will give the manual of a program in PATH called read. That is not the manual for the builtin bash read.

To read bash man page use man bash and search for read [-ers] or simply use:

help read

Which contains this (on version 4.4):

If timeout is 0, read returns immediately, without trying to read any data.

So, no, no data will be read with -t 0.


  1. Q1

    Why it actually doesn’t?

Because that is the documented way it works.

  1. Q2

    How can this be fixed?

Only if that is accepted as a bug (I doubt it will) and the bash source code is changed.

  1. Q3

    And is there, perhaps, alternative solution to the problem of “act differently depending on the presence of the data”?

Yes, actually, there is a solution. The next sentence of help read after what I quoted above reads:

returning success only if input is available on the specified file descriptor.

Which means that even if it doesn’t read any data, it could be used to trigger an actual read of available data:

read -t 0 && read -d '' myData

[ "$myData" ] && echo "got input" || echo "no input was available"

That will have no delay.

Method 2

Disclosure: not a rigorous answer, lots of handwaving

There are several things at play here. First, bash handles zero timeout internally totally differently from non-zero timeouts. (If you decide to look at the code, be prepared for spending some time on it – it is not as straightforward as one might expect; it goes rather deep.)

Second, there’s an inherent race condition when working with pipes – the processes are created “from right to left”, i.e. the last process on the command line is created first. However it is not guaranteed the shell will continue executing, spawning further process from the command line, before the ones already created get scheduled themselves – thus it may well happen that the read is executed before the input is ready. Depending on the particular system, if you play a bit with the timeout value, you may be able to reproduce the -t 0 behaviour with something like -t 0.00001.1)

On top of the above, consider that most systems these days are parallel (either multi-core or multi-processor) – you will get different results depending on how the kernel schedules all the processes in the pipeline (one thing that influences that will be the current system load, btw). This problem may be alleviated, by restricting the process group to a single particular CPU (e.g. with taskset), but the problem with scheduling of child processes mentioned above is not going to go away.


1) I actually managed to get to a point, where read only received part of the input (1 or 2 characters out of 3).


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x