Ola Rozenfeld
Ola Rozenfeld

Reputation: 81

bash read -r -N skips part of the input

This line writes "foo", sleeps, and then writes "bla":

echo "woo"; sleep 2; echo "bla"

I'm trying to read the entire output of this, in two sequential read commands:

(echo "woo"; sleep 2; echo "bla") 1> \
>(IFS=""; read -t 0.1 -r -N 10; \
echo "exit code: $? reply: $REPLY"; \
sleep 5; \
read -t 0.1 -r -N 10; \
echo "exit code: $? reply: $REPLY")

The first read prints:

exit code: 142 reply:

which is kinda expected, since 142 is a timeout, and I called read with -t 0.1. But the second read prints:

exit code: 1 reply: bla

Question: where did "woo" go?!

If I remove the sleep 2 from the output line, the whole thing works as expected -- reads/outputs the entire "woo\nbla" sequence in the first read, and returns 1 (EOF).

The problem reproduces with any sleep, no matter how short. It also doesn't matter if I use a pipe to redirect output instead of >1. This is Ubuntu.

Edit: I want to read into a buffer of N characters, and read the input as-is, without any word-splitting on delimeters. Hence the -N and IFS="".

Edit: this is a toy example to demonstrate a general problem. What I'm trying to do is to implement a smarter version of tee in bash: it should behave like tee, except it should wait for its input-generating process to stop, then flush its buffers, and exit. The real tee hangs indefinitely if the input-generating process starts some zombie child processes, because they then occupy the stdout handle and it never gets closed. Using tail --pid works, but unfortunately it doesn't work on Windows, and I need this to be multi-platform. I thought this could be accomplished by calling read -t -N in a loop, but apparently that doesn't work...

Upvotes: 7

Views: 847

Answers (2)

Dzienny
Dzienny

Reputation: 3417

Question: where did "woo" go?!

The woo was read by the first read -t 0.1 -r -N 10 and then this command failed, because it did not receive 10 characters or EOF character within the set timeout.

According to the read man page:

  -N nchars   return only after reading exactly NCHARS characters, unless
      EOF is encountered or read times out, ignoring any delimiter
  -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

Edit: I want to read into a buffer of N characters, and read the input as-is, without any word-splitting on delimeters. Hence the -N and IFS="".

If you call read command two times, each instance of it has a separate buffer. To solve this use a single read command and adjust or remove the timeout option eg:

(echo "woo"; sleep 2; echo "bla") 1> \
>(IFS=""; read -r -N 10; \
echo "exit code: $? reply: $REPLY";)

And then, if you want to simultaneously view the incoming input, you can add the tee command, eg:

(echo "woo"; sleep 2; echo "bla") 1> \
>(tee >(IFS=""; read -r -N 10; \
echo "exit code: $? reply: $REPLY";))

Upvotes: 6

iamauser
iamauser

Reputation: 11469

You need to introduce a sleep right before reading the output from stdout. This sleep should be at least the same or larger amount that the input.

 (echo "woo"; sleep 1; echo "blah") 1> >( IFS=""; \
   sleep 1; read -t 0.1 -N 10; echo $REPLY; sleep 2; \
   read -t 0.1 -N 10; echo $REPLY)

In your case echo woo gets gobbled up by the following sleep command. By the time output redirection happens, all it sees sleep 1; echo "blah".

And you have another problem. You are trying to read 10 characters with -N option, where as you are providing is 3, causing it to exit with code 142. Try with -N 4.

Following will exit with code = 0;

  $ (echo "woo"; sleep 1; echo "blah") 1> >( IFS=""; \
    sleep 1; read -t 0.1 -N 4; echo "code: $?"; \ 
    echo $REPLY; sleep 2; read -t 0.1 -N 4; echo "code: $?"; \
    echo $REPLY)

  code: 0
  woo
  code: 0
  blah

Upvotes: 1

Related Questions