为什么我必须输入ctrl-d两次?

18 投票
3 回答
5546 浏览
提问于 2025-04-17 01:57

为了自己开心,我写了一个Python脚本,这个脚本让我可以用Python来处理一些简单的命令行操作。你只需要提供一个Python生成器表达式,然后这个脚本就会逐个处理它。下面是这个脚本的内容:

DEFAULT_MODULES = ['os', 're', 'sys']

_g = {}
for m in DEFAULT_MODULES:
    _g[m] = __import__(m)

import sys
sys.stdout.writelines(eval(sys.argv[1], _g))

接下来是你可以如何使用它的示例。

$ groups | python pype.py '(l.upper() for l in sys.stdin)'
DBORNSIDE
$ 

按照预期使用的话,它工作得非常好!

但是当我没有通过管道输入数据,而是直接调用它时,比如说:

$ python pype.py '("%r\n" % (l,) for l in sys.stdin)'
fooEnter
barEnter
bazEnter
Ctrl DCtrl D'foo\n'
'bar\n'
'baz\n'
$ 

为了停止输入并产生输出,我必须输入 Enter - Ctrl D - Ctrl D 或者 Ctrl D - Ctrl D - Ctrl D。这让我感到困惑,因为我原本以为每一行输入后都应该立即处理,而且在任何时候输入 Ctrl D 都应该结束这个脚本。我的理解哪里出了问题呢?

补充说明:我更新了互动示例,显示我没有看到他在回答中提到的引号问题,还有一些其他的示例。

$ python pype.py '("%r\n" % (l,) for l in sys.stdin)'
fooCtrl DCtrl DbarEnter
Ctrl DCtrl D'foobar\n'
$ python pype.py '("%r\n" % (l,) for l in sys.stdin)'
fooCtrl VCtrl D^DbarEnter
Ctrl DCtrl D'foo\x04bar\n'
$ 

3 个回答

0

我不能确切地说为什么需要额外的 CTRL+D(不过其他回答对此解释得很好),但这样做可以让输入在只按一次 CTRL+D 后就被打印出来,不过你还是需要再按一次 CTRL+D 才能退出脚本。

#!/usr/bin/python
DEFAULT_MODULES = ['os', 're', 'sys']

_g = {}
for m in DEFAULT_MODULES:
    _g[m] = __import__(m)

import sys
for x in eval(sys.argv[1], _g):
    print x,

输出:

[ root@host ~ ]$ ./test.py '(l.upper() for l in sys.stdin)'
abc
def(ENTER, CTRL+D)
ABC
DEF
qwerty(ENTER, CTRL+D)
QWERTY
[ root@host ~ ]$

补充:

eval 在这种情况下返回的是一个生成器,所以第一次按 CTRL+D 可能结束了对 sys.stdin 的读取,而第二次则停止了 eval 生成的生成器。

生成器 - 这是一个返回迭代器的函数。它看起来和普通函数差不多,但里面有 yield 语句,用来生成一系列可以在 for 循环中使用的值,或者可以通过 next() 函数一个一个获取。每次 yield 暂时暂停处理,记住执行状态的位置(包括局部变量和待处理的 try 语句)。当生成器恢复时,它会从暂停的地方继续(这和每次调用时都重新开始的普通函数不同)。

生成器类参考(第 9.10 节)

3

Ctrl+D 是终端设备识别的一个组合键,按下它后,终端会生成一个“文件结束”的信号。也许下面的内容能帮你理解,这段话来自维基百科(我强调的部分):

在 UNIX 和 AmigaDOS 系统中,终端驱动程序会将这个按键转换为文件结束信号,所以程序不需要区分终端和其他输入文件。默认情况下,驱动程序会把行首的 Control-D 字符转换为文件结束的指示符。如果你想在输入流中插入一个真正的 Control-D(ASCII 04)字符,用户需要在它前面加上一个“引用”命令字符(通常是 Control-V,不过在某些系统上你可以通过连续按两次 Control-D 来实现这个效果)。

14

Ctrl-D 并不总是被当作文件结束符(EOF),而是被视为“结束当前的 read() 调用”。

如果你在输入框里什么都没输入(或者只是按了 Ctrl-D),再按一次 Ctrl-D,你的 read() 就会立刻结束,并返回0个读取的字节。这时候就表示文件结束了。

如果你在一行里输入了一些内容,然后按 Ctrl-D,你的 read() 会结束,并返回你输入的内容,当然不会有换行符('\n')。

所以,如果你有输入数据,按两次 Ctrl-D 在非空行上,或者在空行上按一次 Ctrl-D,也就是在按了 Enter 之后。

这些规则适用于正常的操作系统接口,可以通过 os.read() 在 Python 中访问。

Python 的文件对象和文件迭代器会把第一次识别到的 EOF 当作当前 read() 调用的结束,因为它们认为没有更多的数据了。下一次 read() 调用会再试一次,需要再按一次 Ctrl-D 才能真正返回0个字节。原因是文件对象的 read() 总是尽量返回请求的字节数,如果操作系统的 read() 返回的字节数少于请求的,就会尝试填充。

file.readline() 不同,iter(file) 使用内部的 read() 函数来读取,因此总是需要额外的 Ctrl-D

我总是使用 iter(file.readline, '') 来逐行读取文件。

撰写回答