The select() or poll() (or pselect()/ppoll() variants) when called with a 0 timeout can be used to check whether a read() system call would immediately succeed.
In bash, on systems that have a select() system call (all the POSIX ones at least) read -t0 does that: -t0 is a special case where it does a select(1, [0], NULL, [0], {tv_sec=0, tv_nsec=0}), checking for both read and error condition on fd 0 (stdin), and returns success if fd 0 is returned in the read set without doing any reading.
With zsh, you can do the same with its zselect builtin (in the zsh/zselect loadable module), which is a raw interface around the select() system call.
zmodload zsh/zselect
if zselect -t 0 -r 0; then
print "A read() on fd 0 (stdin) would succeed immediately"
fi
A read() however can succeed without timeout because there's data to read, but also because end-of-file has been reached, in which case read() reads nothing but succeeds.
So in the general case, it's not the right approach to test whether there's anything to read now.
For that, you can use the FIONREAD ioctl() instead which returns the number of bytes that can be read on a file descriptor.
On Linux at least, that works when the fd is open at least on
- a regular file
- a tty device
- a pipe (as used by
| in most shells)
- a Unix domain socket or socketpair (as used by ksh93's
| for instance), and presumably other types of sockets though I've not tested it.
It doesn't work on some other device files, including /dev/{null,full,zero,random} and block devices.
I don't know of any shell that has a usable interface to that ioctl though¹, but you could resort to perl for instance:
if
perl -le 'require "sys/ioctl.ph";
ioctl(STDIN, &FIONREAD, $n) or die$!;
exit(unpack("L", $n) == 0)'
then
echo "There is something to read on fd 0 (stdin)"
fi
However, it's not because that returns true that a read builtin of your shell would not hang. First there's a TOCTOU race: something else which also has a fd open on the resource on stdin could read what's there by the time you call read.
Then the read builtin reads a full line (without -r even possibly multiple lines if they end in backslashes) for which it may call the read() system call several time (one byte at a time when the input is not seekable) until a newline character is found, and while the first read() will return something, the next ones may hang.
For instance, if stdin is the reading end of a pipe fed by (printf foo; sleep 3600; printf 'bar\n'), FIONREAD will return 3 (the 3 bytes in foo), but read var will hang for an hour to read the full foobar\n line.
Same if stdin is a terminal, and the user has typed foo then Ctrl+d.
The only thing that is guaranteed not to hang (if nothing else has already read the input in the interval) is a single read() system call or multiple read()s as long as the n bytes returned by FIONREAD have not been read yet.
Here, you could however use read -t:
if
perl -le 'require "sys/ioctl.ph";
ioctl(STDIN, &FIONREAD, $n) or die$!;
exit(unpack("L", $n) == 0)'
then
echo "There is something to read on fd 0 (stdin)"
if IFS= read -rt 0.05 line; then
printf 'A full line was read: "%s"\n' "$var"
else
printf 'However, not a full line yet. So far, I'\''ve read: "%s"\n' "$line"
fi
else
echo "Nothing to read from stdin at this point, or it's not of a type where I can tell"
fi
Note the 0.05 timeout, which is short but not much so as we need to give bash enough time to call select() and 1-byte² read()s multiple times until what's to be read has been read.
¹ bash's read -t0 does use FIONREAD where select() is unavailable, but you'll have a hard time finding a system that has FIONREAD but not select() these days.
² for non-seekable input such as ttys or pipes/sockets.