Python 为什么我必须按Ctrl+;D两次关闭stdin?

Python 为什么我必须按Ctrl+;D两次关闭stdin?,python,bash,stdin,Python,Bash,Stdin,我有下面的Python脚本,它读取数字并在输入不是数字时输出错误 import fileinput import sys for line in (txt.strip() for txt in fileinput.input()): if not line.isdigit(): sys.stderr.write("ERROR: not a number: %s\n" % line) 如果我从stdin获得输入,我必须按Ctrl+D两次才能结束程序。为什么? 当我自己运行

我有下面的Python脚本,它读取数字并在输入不是数字时输出错误

import fileinput
import sys
for line in (txt.strip() for txt in fileinput.input()):
    if not line.isdigit():
        sys.stderr.write("ERROR: not a number: %s\n" % line)
如果我从stdin获得输入,我必须按Ctrl+D两次才能结束程序。为什么?

当我自己运行Python解释器时,只需按Ctrl+D一次

bash $ python test.py
1
2
foo
4
5
<Ctrl+D>
ERROR: not a number: foo
<Ctrl+D>
bash $
bash$python test.py
1.
2.
福
4.
5.
错误:不是数字:foo
猛击$

第一次它认为它是输入,第二次它是永久的


这仅在输入来自tty时发生。这可能是因为终端设置,在输入换行符(回车符)之前,字符会被缓冲。

我在回答这个问题时对此做了解释


简而言之,终端上的Control-D只会导致终端刷新输入。这使得
read
系统调用返回。它第一次以非零值返回(如果您键入了某些内容)。第二次,它返回0,这是“文件结束”的代码。在Python 3中,这是由于。这个错误在Python3.3中得到了修复


在Unix终端中,键入Ctrl+D实际上并不会关闭进程的stdin。但是键入Enter或Ctrl+D确实会导致OS
read
系统调用立即返回。因此:

>>> sys.stdin.read(100)
xyzzy                       (I press Enter here)
                            (I press Ctrl+D once)
'xyzzy\n'
>>>
sys.stdin.read(100)
被委托给
sys.stdin.buffer.read
,它在循环中调用系统read(),直到它累积了请求的全部数据量;或者系统read()返回0字节;或者发生错误

在第一行后按Enter键导致系统read()返回6个字节
sys.stdin.buffer.read
再次调用read()以获取更多输入。然后我按下Ctrl+D,导致read()返回0字节。此时,
sys.stdin.buffer.read
放弃了,只返回了之前收集的6个字节

请注意,进程的stdin上仍然有我的终端,我仍然可以键入内容

>>> sys.stdin.read()        (note I can still type stuff to python)
xyzzy                       (I press Enter)
                            (Press Ctrl+D again)
'xyzzy\n'
嗯。这是最初提出这个问题时被打断的部分。现在可以了。但是在Python3.3之前,有一个bug

这个bug有点复杂——基本上问题是两个独立的层在做相同的工作
BufferedReader.read()
被编写为反复调用
self.raw.read()
,直到返回0字节。但是,原始方法,
FileIO.read()
,执行循环直到其自身的字节数为零。因此,在Python中第一次按Ctrl+D键时出现此错误,会导致
FileIO.read()
返回6个字节到
BufferedReader.read()
,然后会立即再次调用
self.raw.read()
。第二个Ctrl+D将导致返回0字节,然后
BufferedReader.read()
将最终退出


不幸的是,这个解释比我之前的解释长得多,但它的优点是正确的。bug是这样的…

这很可能与Python有关,包括以下Python问题:

  • sys.stdin.read()
  • 对于sys.stdin中的行:
    第一次没有注意到EOF
使用从文件中读取行的“for line in file:”形式,Python使用隐藏的预读缓冲区(请参见file.next函数)。首先,这解释了为什么在读取每个输入行时写入输出的程序在按下CTRL-D键之前不会显示输出。其次,为了让用户对缓冲区进行某种控制,按下CTRL-D键会将输入缓冲区刷新到应用程序代码中。当输入缓冲区为空时按CTRL-D将被视为EOF

把这些联系起来回答了最初的问题。输入一些输入后,第一个ctrl-D(在一行上)将输入刷新到应用程序代码。既然缓冲区是空的,那么第二个ctrl-D将充当文件结尾(EOF)


file.readline()
没有这种行为。

我在OSX中没有这种效果。但是,如果我在命中5后直接命中(没有中间的回车符),我会这样做,甚至
cat
也会这样做。@Kristo:您的示例应该格式化为在
5
的同一行上显示
。如果您看到您的示例到目前为止所显示的行为,则说明有问题。@Alok:我的示例的格式与我键入的格式完全相同。如果我将代码更改为使用
sys.stdin.readlines()
,那么第一个代码将结束程序。@Kristo Strange。注意当显示“非数字”时。。。这也是你所期望的吗?你试过几种终端模拟器吗?你在用什么平台?如果是Linux,你能试试控制台吗(你可能可以通过键入Ctrl-Alt-F2获得一个)?@Pascal Cuoq:是的,我在Linux上。我在xterm、GNOME终端、Konsole和控制台中得到了相同的结果。实际上,我希望在输入'foo'后立即显示错误消息,但无论我以何种方式编写代码,直到第一次Ctrl+D之后才显示。这是我不理解的。我的示例中的第一个字符位于新行的第一个字符上,因此我希望它充当EOF。如果我将代码改为使用
sys.stdin.readlines()
,那么第一个将结束程序。这可能是一个bug:应该是这样。虽然我不能复制它(一个代码> Ctrl +d < /Cord>)足以结束<代码> sys .STDIN。()(代码)>如果在Python 2和3上都按下了Enter——您只需在一行中按下“代码> CTRL+D <代码>两次(ICANON标志被设置))。不在操作系统的终端实现中。正如您所说,Ctrl+D在两次按下时都发送EOF。但是
sys.stdin.read()
的实现是一个简单的循环,不断调用
read
,直到返回零字节。如果不清楚:我已经在Python2和3上对它进行了测试:单Ctrl+DII