Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

bash read -r -N skips part of the input

Tags:

bash

ubuntu

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...

like image 323
Ola Rozenfeld Avatar asked Jan 15 '18 17:01

Ola Rozenfeld


People also ask

How to catch a line of input in a bash script?

The exit status is greater than 128 Interactive bash scripts are nothing without catching user input. The read builtin provides methods that user input may be caught within a bash script. To catch a line of input NAMEs and options are not required by read. When NAME is not specified, a variable named REPLY is used to store user input.

What is the use of read in Bash?

Read is a bash builtin command that reads the contents of a line into a variable. It allows for word splitting that is tied to the special shell variable IFS. It is primarily used for catching user input but can be used to implement functions taking input from standard input. Bash read builtin command help

How do I get user input without showing up in Bash?

When catching user input without it showing up int the terminal, the -s option comes in handy. read -s -p allows you to catch and hide user input as follows. Here are examples of functions in bash that use read and standard input Functions using read make use of piped standard input and parameters.

How do I read input from the keyboard in Linux?

To read input from the keyboard and assign input value to a variable use the read command. If you don’t give any argument to the read command, input will assign to the environment variable REPLY. The “s” option does not echo input while reading from a keyboard. The -p “TEXT” option displays TEXT to the user without a newline.


1 Answers

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";))
like image 71
Dzienny Avatar answered Sep 28 '22 03:09

Dzienny