【问题标题】:How to clear stdin before getting new input?如何在获得新输入之前清除标准输入?
【发布时间】:2016-08-11 10:21:58
【问题描述】:

我已经阅读了大约 5-10 条关于如何清除标准输入的不同建议,但没有一个适合我的需要。问题是fflush(stdin) 在我的电脑上运行良好,但不幸的是它似乎并不能在任何地方运行,所以我需要具有相同功能的东西。我尝试过的所有其他方式都会在标准输入不为空时清除它,但在标准输入为空时需要用户输入,这意味着它需要在我不想得到任何输入的时刻输入(+ 它无论如何都会丢弃它)。

问题是:在我要求用户输入之前,我能否以某种方式确保 stdin 为空? (如果不是,那么然后才以某种方式清除它?) 类似于:

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

编辑:问题是我从stdin 一个接一个地加载字符,在某些时候,前一次迭代的一部分输入可能会或可能不会被丢弃。无论哪种方式,在我要求用户处理另一个输入之前,我都需要明确 stdin。清除缓冲区本身并不是什么大问题,问题是当程序到达清除stdin 时输入为空时会发生什么,因为在那一刻程序需要另一个输入,它将被吃掉清算功能。这就是我想要摆脱的。 (当我可以使用fflush(stdin); 时,我只知道,对于我的程序的下一行,stdin 无论如何都将为空,不问任何问题......)

【问题讨论】:

  • fflush(stdin) 是 UB。
  • 不存在空I/O流,它是。考虑:./myprog </dev/random;它会看到其标准输入流的结尾吗?
  • 在windows上编程,必须在linux下工作...
  • 您是否也想刷新任何部分输入的输入行(即用户尚未输入 ENTER 提交的输入)?

标签: c stdin getchar


【解决方案1】:

如何在获得新输入之前清除标准输入?
..所以我需要具有相同功能的东西。

使用便携式 C 是不可能的。


建议使用不同的(更常见的 C)范例:
确保之前的输入函数消耗所有之前的输入。

fgets()(或 *nix getline())是典型的方法,可以解决大多数情况。

或者自己动手。以下内容读取整行,但不保存额外的输入。

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;
}

【讨论】:

    【解决方案2】:

    TL;DR fflush(stdin) 按照标准调用 undefined behavior,你不应该使用它。


    来到你的代码(逻辑),而不是寻找换行符,你可以寻找EOF。在运行此循环之前,stdin 不具备 some 输入的先决条件。

    类似

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

    应该能满足你的需求。

    【讨论】:

    • while (getchar() != EOF);让我陷入一个无限循环,需要一个又一个输入。我也尝试过 if (!feof(stdin)) 等,但都没有奏效。似乎stdin没有设置任何EOF,只有在到达结束时才需要另一个输入。
    • 至少在windows你需要用Ctrl+Z为stdio显式生成EOF。
    • 呃,没有。您的循环一直等到用户生成 EOF(^D^Z),而 OP 要求刷新输入缓冲区的方法。
    【解决方案3】:

    仅使用 fgets() 读取stdin

    使用足够大的缓冲区和/或测试完整的行。

    使用fgets(),您永远不必担心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
    

    【讨论】:

    • 如果tmp[0] == 0if (tmp[strlen(tmp) - 1] != '\n') 是 UB。很容易让fgets() 读取的第一个字符成为空字符。
    • @chux:对,但不是在 text 文件上。 文本 文件(stdin、键盘等)没有嵌入的 NUL。无论如何,代码已更改以处理该问题。谢谢
    • 没错,第一个字符是空字符并不常见。 文本文件的定义不是正式的,并且通常不明确排除'\0'。然而,读取缺少 BOM(认为它是简单的 ASCII)文件的 UTF-16BE text 文件的情况并不少见,该文件很容易有一个前导空字符。键盘是否可以生成空字符是平台级别的问题,超出了程序的控制范围。 IMO 这是一个黑客利用和强大的代码应对。投票支持改进的答案。
    【解决方案4】:

    similar question,使用poll(),fds.fd 设置为 0 (stdin),fds.events 设置为 POLLIN,nfds 设置为 1,超时设置为零。调用 poll() 后,如果缓冲区为空,fds.revents 将被设置为零,否则为 POLLIN。

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

    此解决方案适用于 posix 兼容系统,但不适用于 Windows。使用select() 实现可移植性。

    【讨论】:

      【解决方案5】:

      select 模块提供了一个名为select 的函数,可以完全满足您的需求。 select.select 接受三个参数:

      select.select(rlist, wlist, xlist)
      

      每个参数应该是一个文件描述符列表(例如[sys.sdtin]),然后它会等待直到特定的 IO 操作可用。 IO 操作是对给定文件描述符的read、write 或其他一些exception。它返回一个tuple 对应列表,其中填充了准备好的文件描述符。

      所以,如果sys.stdin 中有输入等待,那么函数的行为将如下所示:

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

      这本身并不能解决您的问题,因为默认情况下,该函数将等待 IO 操作可用。然而,重要的是,select.select 有一个可选的timeout 参数,表示它会在放弃之前等待多长时间。我们只需将超时设置为零,我们就可以在不阻塞程序流程的情况下检查输入。

      我们来看一个sys.stdin中没有输入等待的例子:

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

      知道我们只想要那个元组的第一个元素(输入流),我们准备好创建一个有用的 if 语句:

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

      这意味着清除输入流只需要一些迭代:

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

      我们当然可以在任何输入流上使用它,所以让我们把它放在一个函数中:

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

      所以让我们演示一下我们的功能,以实现您在问题中要求的内容:

      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)
      

      clear_input 函数可以作为一种非阻塞方式来清除输入流,应该可以在 Python2 和 Python3 中使用。

      【讨论】:

      • 但是您确实注意到这是一个 C 问题,而不是 python 问题?
      • 不过,答案很好。只是需要一个不同的问题。
      • 我想 TLDR 的答案是“使用 Python”:-)
      • 正在寻找 Python 解决方案。不幸的是,这在 Windows 上不起作用。我收到:OSError: [WinError 10038] An operation was attempted on something that is not a socket.
      • @howdoicode 看起来你需要这个答案:stackoverflow.com/a/46811177
      猜你喜欢
      • 2019-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多