如何在获取新输入之前清除标准输入(stdin)?

18

我已经阅读了5-10个关于如何清除stdin的建议,但它们都不符合我的需求。问题在于,在我的电脑上fflush(stdin)可以完美地工作,但不幸的是它似乎并不适用于所有情况,所以我需要具有相同功能的替代方法。我尝试的其他每种方式都会在stdin不为空时清除它,但当stdin为空时需要用户输入,这意味着它需要在我不想要任何输入的时候进行输入(而且无论如何都会丢弃输入)。

问题是:我是否可以确保在需要用户输入之前stdin为空?(如果不是,则仅在需要时清除它?) 类似以下内容:

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

编辑:问题在于我逐个从stdin中加载字符,并且在某些时候,前一次迭代的输入可能会被保留或丢弃。无论哪种情况,我都需要在请求另一个要处理的输入之前清楚stdin。清除缓冲区本身并不是什么大问题,问题在于当程序到达清除stdin的点时,输入为空时会发生什么,因为此时程序需要另一个将被清除函数吃掉的输入。这就是我想要摆脱的问题。(当我可以使用fflush(stdin);时,我知道对于程序的下一行,stdin无论如何都将为空,没有任何问题...)


6
fflush(stdin) 是未定义行为。 - Sourav Ghosh
1
不存在“空的I/O流”,它是一个“流”。考虑:./myprog </dev/random;它会看到stdin流的末尾吗? - joop
编程在Windows上,必须在Linux上工作... - Tom
您是否想刷新任何未完全键入的输入行(也就是用户尚未键入 ENTER 键以提交的输入)? - Mark Plotnick
1
@Jens,apt-get程序是有意这样做的。请参见Prevent sudo, apt-get, etc. from swallowing pasted input to STDIN - Mark Plotnick
5个回答

7
如何在获取新输入之前清除标准输入?..所以我需要具有相同功能的东西。 使用可移植的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;
}

1

从一个相似的问题中,使用 poll() 函数并将 fds.fd 设置为 0(stdin),fds.events 设置为 POLLIN,nfds 设置为 1,timeout 设置为零。调用 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()以实现可移植性。


0

简而言之 fflush(stdin) 根据标准会引发未定义行为,因此你永远不应该使用它。


关于您的代码(逻辑),您可以寻找EOF而不是寻找换行符。这并不需要在运行此循环之前stdin应该有一些输入。

类似于以下内容:

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

应该能够满足您的需求。


当我使用while (getchar() != EOF);时,它会使我陷入一个无限循环,需要不断输入。我也尝试了if (!feof(stdin))等等,但都没有起作用。似乎stdin没有设置任何EOF,只有在到达结尾时才需要另一个输入。 - Tom
1
至少在Windows上,您需要使用Ctrl+Z显式生成stdio的EOF。 - HolyBlackCat
2
没有。你的循环会等待用户生成EOF('^D'或'^Z'),而OP要求一种清除输入缓冲区的方法。 - jch

0

使用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] == 0,那么 if (tmp[strlen(tmp) - 1] != '\n') 就会出现未定义行为。很容易通过让 fgets() 读取到的第一个字符是空字符来解决这个问题。 - chux - Reinstate Monica
@chux: 是的,但这并不适用于文本文件。文本文件(stdin,键盘等)没有嵌入的 NUL 字符。代码已经进行了更改以解决此问题。谢谢 - pmg
是的,第一个字符不是空字符并不常见。_文本文件_的定义并不正式,通常也没有明确排除'\0'。然而,读取缺少BOM(认为它是简单的ASCII)的UTF-16BE 文本文件并不罕见,这种文件很容易有一个前导空字符。键盘是否能生成空字符是平台级问题,超出程序的控制范围。在我看来,这是黑客的攻击手段,健壮的代码可以应对。支持改进答案。 - chux - Reinstate Monica

-4

select 模块提供了一个名为 select 的函数,它可以实现您要寻找的功能。 select.select 接受三个参数:

select.select(rlist, wlist, xlist)

每个参数都应该是文件描述符列表(例如[sys.stdin]),然后等待特定的IO操作可用。IO操作包括给定文件描述符上的r读取,w写入或其他x异常。它返回一个元组,其中包含准备就绪的文件描述符对应的列表。

因此,如果在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。


5
但您确实注意到这是一个C语言问题,而不是Python问题了吗? - Jens
2
很好的答案,只不过需要提出一个不同的问题。 - Mark Plotnick
2
我想TLDR的答案是“使用Python” :-) - dsclose
正在寻找 Python 解决方案。不幸的是,这在 Windows 上不起作用。我收到以下信息:OSError: [WinError 10038] 尝试对非套接字执行操作。 - howdoicode
@howdoicode 看起来你需要这个答案:https://stackoverflow.com/a/46811177 - dsclose
有点啰嗦,但这是一个合理的答案。OP需要将其翻译成所选的目标语言。 - SoloPilot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接