Usually while working with pipes and stdin, a depleted pipe has no special meaning. New data may still appear until there is an eof
that closes the pipe. Your cat
terminates at eof
as expected. If there was no data before eof
, only then you can say the stdin was truly empty.
Consider sender | receiver
. It's not uncommon the sender
is (much) slower than the receiver
; in such case the receiver
's stdin is almost always depleted, but you hardly ever want to kill the entire pipe because of it. Therefore tools that exit on "empty" (depleted but not yet terminated) stdin are exceptions rather than standard.
In Bash there is read -t 0
(-t
is not required by POSIX). From help read
:
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.
By default read
reads from stdin, so the exit status of read -t 0
will tell you if the stdin is "empty". But beware! A command like
echo 1 | read -t 0
may exit successfully or not, because echo
and read
run simultaneously, not sequentially. To avoid this, your script should sleep
for a while before read -t 0
. Depending on where the stdin comes from, "a while" may be relatively long. Do something like this:
sleep 1
if read -t 0; then … # process stdin here, you know it's non-empty
You populate a variable with data taken from stdin. Since storing binary data in a variable is not a good idea (read this), maybe your data is just text. If so, use read -t
like this:
read -r -t 5 -d $'\0' stdin
Null character (which you cannot store in a Bash variable anyway) as a delimiter (-d $'\0'
) will allow you to read any text (e.g. with newlines) to the stdin
variable. After at most 5 seconds (-t 5
) the command terminates, allowing your script to continue.
Another approach is with timeout
. A basic example from my Debian:
timeout --foreground 5 cat | wc -c
(Replace wc -c
with your code that parses stdin; it's just an example).
This should handle binary data just fine. If cat
doesn't get eof
then after 5 seconds it will be killed, so wc
will get eof
anyway and the line doesn't stall. The problem is cat
will be killed regardless if it's processing any data at the moment. I imagine you want to get all the data, if only there is some, even if it takes more than 5 seconds. Improved version:
{ timeout --foreground 5 dd bs=1 count=1 2>/dev/null && cat; } | wc -c
If the first byte appears within 5 seconds, cat
will be triggered. It then will process any further input until eof
, no matter how long it takes. Everything including the first byte (if any) will go to wc
. If there is neither a byte nor eof
in 5 seconds, wc
will receive just eof
; the line doesn't stall.
I would be surprised if a chroot without at least /dev/null was usable in production. There's a long way between "/dev mounted" and "/dev manually created with the bare minimum", anyway... – user1686 – 2018-09-27T14:45:54.763
No solution but
cat -
or even a simplecat
would do the same as yourcat <&0
. – xenoid – 2018-09-27T18:07:21.593@xenoid yes, I know. I tried all three, and they all produce identical results. <&0 is just leftover from playing around to see if I can make cat error out rather than wait. – StarCrashr – 2018-09-27T18:24:58.560
By the way, I continued to play with it while waiting for an answer and discovered that it wasn't just hanging. It was looking for input from the console. This lead to to experimenting with heredocs to attempt to terminate cat if it has no input, but got an answer before getting far with that. – StarCrashr – 2018-09-27T19:51:06.710