19

I have the following bash script:

# do some time consuming task here
read -p "Give me some input: " input

Now as you might have guessed, if a user presses some random keys during the "time consuming task", the unwanted input is taken into account as well. How do I clear stdin (or at least ignore it) before I issue the read command?

Duijf
  • 689
  • 4
  • 17
rabin
  • 305
  • 2
  • 3
  • 7
  • 1
    Myself, unless you are writing a curses-like program, I find what you wish to do to be a design flaw in your program. UNIX/Linux has the very useful feature of buffering input ("type-ahead") and I commonly make use of this functionality. Coming across your program where you throw away what I typed, I would likely submit a bug and stop using your program until it was fixed. – Arcege Apr 29 '11 at 12:13
  • 1
    Some users have annoying habit of playing piano with their keyboard while their program is busy doing something. I would rather throw away those keystrokes and start fresh. But you're right, the "type-ahead" is useful, but not always. – rabin Apr 29 '11 at 15:52

6 Answers6

23

In Bash 4, you can set -t (timeout) to 0. In this case, read immediately returns with an exit status indicating whether there's data waiting or not:

# do some time consuming task here
while read -r -t 0; do read -r; done
read -p "Give me some input: " input
christophjaeger
  • 331
  • 2
  • 3
  • 1
    This only seems to work if the user pressed enter after the keys. If the user has just typed random keys (& no enter press), then the loop exits immediately without clearing the buffered characters. – btalb Mar 10 '20 at 06:06
16

I don't think there is a way to clear stdin but (with bash) you can read and discard what is there before you ask for the input

#do some time consuming task here
read -t 1 -n 10000 discard 
read -p "Give me some input: " input

This reads stdin and has a timeout of 1 second, it fails though if there are more than 10000 chars in stdin. I don't know how big you can make the nchars parameter.

  • I actually had found this hack on a forum. I was expecting to find a better way of doing it. Apparently not. – rabin Apr 28 '11 at 16:09
  • @rabin: If you do find a better way post back here I have it in a few scripts. –  Apr 28 '11 at 16:11
  • Unfortunately this doesn't work with all shells, e.g. *dash* :( – scai Dec 07 '13 at 09:53
7
function clear_stdin()
(
    old_tty_settings=`stty -g`
    stty -icanon min 0 time 0

    while read none; do :; done 

    stty "$old_tty_settings"
)

clear_stdin
  • 2
    If you leave out word "function", it would work by `dash` and by any other POSIX compliant shell. – jarno Mar 18 '20 at 00:57
  • 1
    This is one of very few cases where you shouldn't double-quote a variable. Use `stty $old_tty_settings` without quotes. The reason is `stty -g` generates output that is unspecified. Implementations of `stty` are allowed to generate output with spaces and later expect the shell to split it and pass multiple arguments. What is specified is a constraint that the output of `stty -g` must be safe when unquoted, it must not trigger word expansion in a shell. – Kamil Maciorowski Dec 01 '20 at 15:36
  • @KamilMaciorowski I don't get it, is there a downside to the double quotes other than the fact that they aren't necessary? – kaan_a Aug 04 '21 at 09:59
  • 3
    @kaan_a When quoted, the command may not work. It totally depends on the implementation of `stty`. The [specification](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/stty.html#tag_20_123) states the tool should understand its own output (i.e. the output of `stty -g`) as argument**s** (plural!) and the output shall not require quoting. If quoted, the string will be a single argument. The specification does not mention "argument" (singular), `stty` is not required to understand it in this form, it may or *may not* understand it. Not quoting is the only way that is *required* to work. – Kamil Maciorowski Aug 04 '21 at 13:30
7
read -d '' -t 0.1 -n 10000

This reads multiple lines of inputs, if the user inadvertently pressed enter multiple times

Simon Sheehan
  • 9,114
  • 12
  • 52
  • 71
Ram Natarajan
  • 71
  • 1
  • 1
5

this worked well for me:

function clean_stdin()
{
    while read -e -t 0.1; do : ; done
}
pschichtel
  • 151
  • 1
  • 3
4

Enclose the time consuming task in a block whose stdin is closed:

{
     # time consuming task
} <&-

read -p "Give me some input: " input
wnrph
  • 3,633
  • 1
  • 26
  • 39
  • I believe that this has nothing to do with the question. – Scott - Слава Україні Dec 05 '16 at 20:29
  • But it does! The user wants all input discarded. Not allowing input does exactly that -- since stdin is closed, all input is discard (by the system). It's an elegant solution to his problem. He gets the system to do the discard without the trouble of writing a discard loop. – HiTechHiTouch May 01 '17 at 11:26
  • This seems to be the most reliable option so far. – Stephen Eilert May 18 '17 at 20:46
  • Seems perfect but does not work for me. I tried bash 5.0.0 and 4.4.19. `read` will still read the first line entered during `# time consuming` task. Furthermore, if the script doesn't contain any commands that read stdin after the `read` then the unread lines are executed on the interactive terminal after the script terminates. Did anyone successfully test this? – Socowi Feb 27 '19 at 10:56