通常,在Linux终端上连接到标准输入的程序中,要指示EOF,如果我只按了Enter,则需要按一次Ctrl+D,否则需要按两次。但是我注意到patch
命令是不同的。对于它,如果我只按了Enter,则需要按两次Ctrl+D,否则需要按三次。(使用cat | patch
代替这种奇怪行为。此外,如果我在键入任何实际输入之前按Ctrl+D,则没有这种奇怪行为。)深入挖掘patch
的源代码,我追踪到它在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 在评论中指出,在 macOS 上不会发生这种情况。听到这个消息后,我尝试使用同样来自 Ubuntu 16.04 的 musl 1.1.9-1 对相同的程序进行编译,它没有这个问题。在 musl 上,事件序列移除了步骤 12 到 14,这就是为什么它没有这个问题,但除了 readv
替换了 read
这个无关紧要的细节之外,它与 glibc 的行为相同。
现在的问题是:glibc 的行为是否有误,或者 patch 假定其 libc 不会有这种行为是否有误?
read()
使用。如果没有缓冲输入,则不会有任何字节可用,读取零字节表示EOF。 - Jonathan Leffler