I'm not sure you can detect EOF without actually trying to read some non-zero number of bytes.
That's because, well, there's no return value from the read()
system call that explicitly means end-of-file. Instead, all you get is "zero bytes read, no error", and it's up to the application code to know what that means. On a regular file, that obviously happens when you're reading at or past the end of the file, when there's no data left.
But on a terminal it can happen because the user hit ^D on an empty line, causing the terminal interface to return what it has at that point, i.e. nothing; and on a datagram socket, it's possible to send and receive zero-length messages. Neither one of those cases signals an actual end: the terminal can be read for data after a ^D, and a socket might receive other messages after zero-length one. (And even on a regular file, a subsequent might return data -- if some other process appended to the file in the meanwhile. Repeating reads off EOF is what a simple implementation of tail -f
would do.)
And if you explicitly ask to read zero bytes, you also get zero bytes (or an error), regardless of if you're at EOF or not.
Probably the best result could be had if the external program was able to deal with an EOF without too much fuss, preferably just return an exit code signalling that. Then you'd do:
while external_program; do
# do we need to do anything here but loop?
true
done
or, if we're so lucky that we can get a different exit status for EOF:
while true; do
external_program
ret=$?
if [ "$ret" = 0 ]; then
echo "ok, continue"
elif [ "$ret" = 1 ]; then
echo "deal with this error"
# but what now?
elif [ "$ret" = 2 ]; then
echo "got EOF, stopping"
break
fi
done
Having that program deal with EOF makes sense in that it needs to verify what ever input it gets anyway.
If you can't do that, you could have Bash read the block of data and pass it to the program if enough was actually read:
blocksize=123
while IFS= read -d '' -r -n "$blocksize" data && [ "${#data}" = "$blocksize"]; do
printf "%s" "$data" | externalprogram
done
But that only works in Bash if the data never contains NUL bytes (\0
). If it does, you'd need to switch to Zsh (or some real programming language), or use something like head -c "$blocksize" > tmpfile
instead.