【发布时间】:2019-03-11 11:01:39
【问题描述】:
通常,要向附加到 Linux 终端上的标准输入的程序指示 EOF,如果我只是按 Enter,则需要按一次 Ctrl+D,否则需要按两次。不过,我注意到patch 命令是不同的。有了它,如果我只是按 Enter,我需要按 Ctrl+D 两次,否则需要按 3 次。 (而不是cat | patch 没有这种奇怪之处。此外,如果我在输入任何实际输入之前按 Ctrl+D,它就没有这种奇怪之处。)深入研究patch 的源代码,我追踪这回the way it loops on fread。这是一个做同样事情的最小程序:
#include <stdio.h>
int main(void) {
char buf[4096];
size_t charsread;
while((charsread = fread(buf, 1, sizeof(buf), stdin)) != 0) {
printf("Read %zu bytes. EOF: %d. Error: %d.\n", charsread, feof(stdin), ferror(stdin));
}
printf("Read zero bytes. EOF: %d. Error: %d. Exiting.\n", feof(stdin), ferror(stdin));
return 0;
}
当完全按原样编译和运行上述程序时,以下是事件时间线:
- 我的程序调用
fread。 -
fread调用read系统调用。 - 我输入“asdf”。
- 我按 Enter。
-
read系统调用返回 5。 -
fread再次调用read系统调用。 - 我按 Ctrl+D。
-
read系统调用返回 0。 -
fread返回 5。 - 我的程序打印
Read 5 bytes. EOF: 1. Error: 0. - 我的程序再次调用
fread。 -
fread调用read系统调用。 - 我再次按 Ctrl+D。
-
read系统调用返回 0。 -
fread返回 0。 - 我的程序打印
Read zero bytes. EOF: 1. Error: 0. Exiting.
为什么这种读取标准输入的方式会有这种行为,不像其他程序似乎读取它的方式?这是patch 中的错误吗?这种循环应该怎么写才能避免这种行为呢?
更新:这似乎与 libc 有关。我最初在 Ubuntu 16.04 的 glibc 2.23-0ubuntu3 上体验过它。 @Barmar 在 cmets 中指出它不会在 macOS 上发生。听到这个后,我尝试针对同样来自 Ubuntu 16.04 的 musl 1.1.9-1 编译相同的程序,但没有出现这个问题。在 musl 上,事件序列删除了第 12 步到第 14 步,这就是为什么它没有问题,但在其他方面是相同的(除了 readv 代替 read 的不相关细节)。
现在,问题变成了:glibc 的行为是错误的,还是 patch 错误地假设它的 libc 不会有这种行为?
【问题讨论】:
-
至少请参阅Canonical vs non-canonical terminal input。这提到点击“EOF”指示键可使所有缓冲输入对
read()可用。如果没有缓冲输入,它会提供零字节,读取的零字节表示 EOF。 -
@JonathanLeffler 这解释了为什么你必须在一行的开头输入 Ctl-D 来表示 EOF。但这并不能解释为什么他必须这样做两次。
-
@Barmar 另一个重要的细节:您需要输入一些输入而不是立即按 Ctrl+D,否则它可以正常工作。我也会补充的。
-
糟糕,当我以为我在 Linux 上进行测试时,我不在 Linux 上。它在 MacOS 上可以正常工作,但我在 Linux 上看到的和你一样。
-
它是 linux 实现的工件,以及 tty 的工作原理。第一个 CTRL+D 将 asdf\n 发送到您的程序,但 CTRL+D 实际上并没有关闭标准输入。 fread() 继续并且 read() 系统调用阻塞,因为标准输入并没有真正关闭。 fread() 决定放弃下一个 CTRL+D,因为 read() 返回 0 并且其内部缓冲区中没有任何内容。