How to clear stdin before getting new input?
Asked Answered
M

6

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

Metaplasm answered 19/4, 2016 at 9:58 Comment(5)
fflush(stdin) is UB.Yong
There is no such thing as an empty I/O stream , it is a stream. Consider: ./myprog </dev/random ; will it ever see the end of its stdin stream?Ichthyoid
programming it on windows, has to work at linux...Metaplasm
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?Delude
@Jens The apt-get program does this, intentionally. See Prevent sudo, apt-get, etc. from swallowing pasted input to STDINDelude
T
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;
}
Townley answered 19/4, 2016 at 15:35 Comment(0)
I
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.

Ingeringersoll answered 27/7, 2021 at 6:34 Comment(0)
Y
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.

Yong answered 19/4, 2016 at 10:1 Comment(3)
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.Metaplasm
At least at windows you need to explicitly generate EOF for stdio with Ctrl+Z.Likable
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.Tonguelashing
Q
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
Quartern answered 19/4, 2016 at 11:19 Comment(3)
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.Townley
@chux: right, but not on text files. Text files (stdin, the keyboard, ...) do not have embedded NULs. Code changed to deal with that anyway. ThanksQuartern
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.Townley
I
0

The following implementation of getline in standard C shows how to clear stdin :

/* read a line into s, return length */
int getline (char line[], int max_length)
{
  int i;
  char c;
  for (i = 0; i < max_length && (c = getchar()) != '\n'; ++i)
    line[i] = c;
  line[i] = '\n';
  line[i+1] = '\0';

  /* clear stdin if not empty (user entry exceeded max_length */
  if (c != '\n')
    while (getchar() != '\n')
      ;

  return i;
}
Immotile answered 4/5, 2024 at 7:47 Comment(0)
A
-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.

Adila answered 19/4, 2016 at 12:36 Comment(6)
But you did notice that this is a C question, not a python question?Wrier
Great answer, though. Just needs a different question.Delude
I suppose the TLDR answer is "use Python" :-)Adila
Was looking for a Python solution. Unfortunately, this doesn't work on Windows. I receive: OSError: [WinError 10038] An operation was attempted on something that is not a socket.Extensive
@Extensive it looks like you need this answer: stackoverflow.com/a/46811177Adila
A bit verbose, but this is a reasonable answer. OP needs to translate it into target language of choice.Imply

© 2022 - 2025 — McMap. All rights reserved.