为什么要按两次Ctrl+D才能关闭stdin?

15 投票
5 回答
7398 浏览
提问于 2025-04-15 18:38

我有一个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 $

5 个回答

5

我在我回答这个问题的时候写了一些解释。

如何捕捉 Control+D 信号?

简单来说,在终端按下 Control+D 会让终端清空输入。这会导致 read 系统调用返回。第一次返回时,如果你输入了内容,它会返回一个非零的值。第二次返回时,它会返回0,这表示“文件结束”。

9

这很可能与Python的以下问题有关:

  • 5505: 在Windows上,sys.stdin.read()在第一次遇到文件结束符(EOF)后不会返回,
  • 1633941: for line in sys.stdin:第一次遇到EOF时没有反应。
16

在Python 3中,这个问题是因为Python标准输入输出库中的一个错误。这个错误在Python 3.3版本中被修复了。


在Unix终端中,按下Ctrl+D并不会真正关闭进程的标准输入。不过,按下回车键或者Ctrl+D会让操作系统的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字节;或者出现错误。(文档) (来源)

在第一行后按下回车,系统的read()返回了6个字节。sys.stdin.buffer.read又调用read()来尝试获取更多输入。然后我按下Ctrl+D,这时read()返回了0字节。此时,sys.stdin.buffer.read放弃了,只返回了之前收集的6个字节。

注意,进程仍然保持着我的终端作为标准输入,我仍然可以输入内容。

>>> sys.stdin.read()        (note I can still type stuff to python)
xyzzy                       (I press Enter)
                            (Press Ctrl+D again)
'xyzzy\n'

好的。这是当初提问时出问题的部分。现在它可以正常工作了。但在Python 3.3之前,确实存在一个错误。

这个错误有点复杂——基本上问题在于两个不同的层在做同样的事情。BufferedReader.read()是为了反复调用self.raw.read(),直到返回0字节。然而,原始方法FileIO.read()自己也执行了一个循环,直到返回0字节。所以在有这个错误的Python中,第一次按Ctrl+D会让FileIO.read()返回6个字节给BufferedReader.read(),然后它会立刻再次调用self.raw.read()。第二次按Ctrl+D会让那个返回0字节,然后BufferedReader.read()才会最终退出。

这个解释比我之前的要长得多,但它的确是正确的。错误就是这样……

撰写回答