Обычно, чтобы указать EOF на программу, подключенную к стандартным входам на терминале Linux, мне нужно нажать Ctrl + D один раз, если я просто нажал Enter, или два раза в противном случае. Однако я заметил, что команда patch
отличается. При этом мне нужно дважды нажать Ctrl + D, если я просто нажал Enter, или три раза в противном случае. (Это 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.
Read 5 bytes. EOF: 1. Error: 0.
- Моя программа снова вызывает
fread
. -
fread
вызывает системный вызовread
. - Я снова нажимаю Ctrl + D.
-
read
системный вызов возвращает 0. -
fread
возвращает 0. - Моя программа печатает
Read zero bytes. EOF: 1. Error: 0. Exiting.
Read zero bytes. EOF: 1. Error: 0. Exiting.
Почему это означает, что чтение stdin имеет такое поведение, в отличие от того, как каждая другая программа читает его? Это ошибка в patch
? Как следует писать такой тип цикла, чтобы избежать такого поведения?
UPDATE: похоже, это связано с libc. Первоначально я испытал это на glibc 2.23-0ubuntu3 от Ubuntu 16.04. @Barmar отметил в комментариях, что это не происходит на macOS. Услышав это, я попытался составить ту же программу против musl 1.1.9-1, также из Ubuntu 16.04, и у нее не было этой проблемы. На муслисе последовательность событий имеет шаги с 12 по 14, и поэтому она не имеет проблемы, но в остальном readv
та же (за исключением нерелевантных деталей readv
вместо read
).
Теперь возникает вопрос: является ли glibc неправильным в его поведении или неверно исправляется, если предположить, что его libc не будет иметь такое поведение?