18

I have read about 5-10 different advices how to clear stdin, but none of them suits my needs. The thing is that fflush(stdin) worked perfectly at my computer, but unfortunately it doesn't seem to work everywhere, so I need something with the same functionality. Every other way I tried clears stdin when it is not empty but requires user input when stdin IS empty, which means it requires input in a moment I dont want to get any (+ it discards it anyway).

The question is: Can I somehow make sure, that stdin IS empty before I require user input? (and if not, THEN and only then clear it somehow?) something like:

if (stdin is NOT empty) 
    while (getchar() != '\n')
        continue;

EDIT: the thing is that I load characters from stdin one by one and at some point, a part of the input from previous iteration might or might not get discarded. either way, I need to have clear stdin before I ask the user for another input to be processed. Clearing the buffer itself is not such a big deal, the problem is what happens when the input is empty when the program gets to the point of clearing stdin, because in that moment the program needs another input which is going to be eaten by the clearing function. Thats what I want to get rid of. (when I could use fflush(stdin); I just knew, that for the next line of my program the stdin WILL be empty no matter what, no questions asked...)

Tom
  • 181
  • 1
  • 1
  • 4
  • 6
    `fflush(stdin)` is UB. – Sourav Ghosh Apr 19 '16 at 10:00
  • 1
    There is no such thing as an *empty I/O stream* , it is a **stream**. Consider: `./myprog – joop Apr 19 '16 at 10:28
  • programming it on windows, has to work at linux... – Tom Apr 19 '16 at 11:18
  • Do you want to flush any partially-typed lines of input (that is, input for which the user hasn't typed ENTER to commit) as well? – Mark Plotnick Apr 19 '16 at 17:37
  • 1
    @Jens The `apt-get` program does this, intentionally. See [Prevent sudo, apt-get, etc. from swallowing pasted input to STDIN](http://serverfault.com/questions/342697/prevent-sudo-apt-get-etc-from-swallowing-pasted-input-to-stdin) – Mark Plotnick Apr 20 '16 at 15:21

5 Answers5

7

How to clear stdin before getting new input?
.. so I need something with the same functionality.

With portable C this is not possible.


Instead suggest a different (and more usual C) paradigm:
Insure previous input functions consumes all the previous input.

fgets() (or *nix getline()) is the typical approach and solves most situations.

Or roll your own. The following reads an entire line, but does not save extra input.

int mygetline(char *buf, size_t size) {
  assert(size > 0 && size <= INT_MAX);
  size_t i = 0;
  int ch;
  while ((ch = fgetc(stdin)) != EOF) {  // Read until EOF ...
    if (i + 1 < size) {
      buf[i++] = ch;
    }
    if (ch == '\n') {  // ... or end of line
      break;  
    }
  } 
  buf[i] = '\0';
  if (i == 0) { 
    return EOF;
  }
  return i;
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
1

From a similar question, Use poll() with fds.fd set to 0 (stdin), fds.events set to POLLIN, nfds set to 1, and timeout set to zero. After calling poll(), fds.revents will be set to zero if the buffer is empty, and to POLLIN otherwise.

struct pollfd fds = {0, POLLIN, 0};
poll(&fds, 1, 0);
if(fds.revents == POLLIN}
    printf("stdin buffer is not empty");

This solution will work on posix-compliant systems, but not Windows. Use select() for portability.

Jason Fry
  • 11
  • 1
0

TL;DR fflush(stdin) invokes undefined behavior as per the standard, you should never use it.


Coming to your code (logic), instead of looking for a newline, you can look for EOF. It does not have a prerequisite that stdin should have some input before running this loop.

Something like

 while (getchar() != EOF);   //; is not a mistake

should meet your needs.

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
  • while (getchar() != EOF); sets me in an infinite loop requiring another and another input. I also tried if (!feof(stdin)) etc. but none of that worked. It seemes like stdin does not have any EOF set, it only requires another input when the end is reached. – Tom Apr 19 '16 at 10:23
  • 1
    At least at windows you need to explicitly generate EOF for stdio with Ctrl+Z. – HolyBlackCat Apr 19 '16 at 11:20
  • 2
    Er, no. Your loop waits until the user generates an EOF (`^D` or `^Z`), while the OP asked for a way to flush the input buffer. – jch Apr 19 '16 at 15:18
0

Use only fgets() to read stdin.

Use a large enough buffer and/or test for full lines.

Using fgets() you never have to worry about extra characters in stdin.

// read characters until 'X'
while (((ch = getchar()) != EOF) && (ch != 'X')) putchar(ch);
// discard X and the rest of the line
fflush(stdin); // UB except for Windows

// read full line
char tmp[1000], *p;
if (!fgets(tmp, sizeof tmp, stdin)) /* deal with error */;
if (!*tmp) /* embedded NUL detected: input is not a text file */;
if (tmp[strlen(tmp) - 1] != '\n') /* partial line */;
p = tmp;
while (*p && *p != 'X') putchar(*p++);
// ignore the X and all the subsequent characters
pmg
  • 106,608
  • 13
  • 126
  • 198
  • `if (tmp[strlen(tmp) - 1] != '\n')` is UB if `tmp[0] == 0`. Easy enough to have the first character read by `fgets()` to be the null character. – chux - Reinstate Monica Apr 19 '16 at 15:19
  • @chux: right, but not on **text** files. **Text** files (`stdin`, the keyboard, ...) do not have embedded NULs. Code changed to deal with that anyway. Thanks – pmg Apr 20 '16 at 07:44
  • True, it is not common for the first character to be the null character. The definition of a _text file_ is not formal and often does not expressly preclude a `'\0'`. Yet it is not rare either to read a UTF-16BE **text** file that lacks a BOM (thinking it is a simple ASCII) file which can easily have a leading null character. If a keyboard can generate a null character or not is a platform level issue, beyond the program's control. IMO it is a hacker exploit and robust code copes. up vote for improved answer. – chux - Reinstate Monica Apr 20 '16 at 14:13
-4

The select module offers a function called select that achieves exactly what you're looking for. select.select takes three arguments:

select.select(rlist, wlist, xlist)

Each argument should be a list of file descriptors (such as [sys.sdtin]) and it then waits until a specific IO operation is available. The IO operations are read, write or some other exception on the given file descriptors. It returns a tuple of corresponding lists populated with the file descriptors that are ready.

So, if there is input waiting in sys.stdin then the function would behave like so:

>>> import select
>>> import sys
>>>
>>> select.select([sys.stdin], [], [])
([sys.stdin], [], [])
>>>

By itself, this doesn't solve your problem because by default the function will wait until an IO operation is available. Importantly, however, select.select has an optional timeout argument denoting how long it will wait before giving up. We simply have to set the timeout to zero and we can check for input without blocking the program flow.

Let's see an example where there is no input waiting in sys.stdin:

>>> import select
>>> import sys
>>>
>>> timeout = 0
>>> select.select([sys.stdin], [], [], timeout)
([], [], [])
>>>

Knowing that we only want the first element of that tuple (the input streams) we're ready to make a useful if statement:

if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
    print('Input is waiting to be read.')

That means clearing the input stream just needs some iteration:

while sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
    sys.stdin.readline()

And we can of course use this on any input stream, so lets put it in a function:

def clear_input(stream, timeout=0):
    '''Takes an input stream and discards each line in the buffer.
    The given timeout denotes how long in seconds to wait for 
    further input when none is available.
    '''
    while stream in select.select([stream], [], [], timeout)[0]:
        stream.readline()

So let's demonstrate our function to achieve what you ask for in your question:

import select
import sys
import time

def clear_input(stream, timeout=0):
    while stream in select.select([stream], [], [], timeout)[0]:
        stream.readline()

if __name__ == '__main__':
    print('Type some lines now. They will be ignored.')
    time.sleep(5)

    print('Clearing input.')
    clear_input(sys.stdin)

    user_input = raw_input('Please give some fresh input: ')
    print(user_input)

The clear_input function can be used as a non-blocking way to clear input streams and should work in Python2 and Python3.

dsclose
  • 556
  • 5
  • 15