Почему мне нужно дважды нажать Ctrl + D, чтобы закрыть stdin?

У меня есть следующий Python script, который считывает числа и выводит ошибку, если вход не является числом.

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 дважды, чтобы завершить программу. Почему?

Мне нужно только нажать Ctrl + D один раз, когда я запускаю интерпретатор Python сам по себе.

bash $ python test.py
1
2
foo
4
5
<Ctrl+D>
ERROR: not a number: foo
<Ctrl+D>
bash $

Ответы

Ответ 1

В Python 3 это вызвано ошибкой в ​​стандартной библиотеке ввода-вывода Python. Ошибка была исправлена ​​в Python 3.3.


В терминале Unix ввод Ctrl + D фактически не закрывает процесс stdin. Но ввод в Enter или Ctrl + D вызывает системный вызов OS 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 байт; или возникает ошибка. (docs) (source)

Нажатие Enter после первой строки заставляет систему read() возвращать 6 байтов. sys.stdin.buffer.read снова вызвал read(), чтобы попытаться получить больше ввода. Затем я нажал Ctrl + D, заставив read() вернуть 0 байт. В этот момент sys.stdin.buffer.read сдался и вернул только 6 байтов, которые он собрал ранее.

Обратите внимание, что процесс по-прежнему имеет мой терминал на stdin, и я все еще могу напечатать материал.

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

OK. Это та часть, которая была разрушена, когда этот вопрос изначально был задан. Он работает сейчас. Но до Python 3.3 произошла ошибка.

Ошибка была немного сложной - в основном проблема заключалась в том, что два отдельных слоя выполняли одну и ту же работу. BufferedReader.read() было написано многократно называть self.raw.read(), пока оно не вернет 0 байт. Однако необработанный метод FileIO.read() выполнял собственный цикл - до нуля. Итак, в первый раз, когда вы нажмете Ctrl + D на Python с этой ошибкой, это приведет к тому, что FileIO.read() вернет 6 байтов в BufferedReader.read(), после чего немедленно вызовет self.raw.read() снова. Второй Ctrl + D приведет к возврату 0 байтов, а затем BufferedReader.read(), наконец, выйдет.

Это объяснение, к сожалению, намного дольше, чем мое предыдущее, но оно имеет силу быть верным. Ошибки такие...

Ответ 2

Скорее всего, это связано с Python следующими проблемами Python:

  • 5505: sys.stdin.read() не возвращается после первого EOF в Windows и
  • 1633941: for line in sys.stdin: не замечает EOF в первый раз.

Ответ 3

Я написал объяснение об этом в своем ответе на этот вопрос.

Как захватить сигнал Control + D?

Короче говоря, Control-D на терминале просто заставляет терминал сбросить ввод. Это приводит к возврату системного вызова read. Первый раз возвращается с ненулевым значением (если вы что-то набрали). Во второй раз он возвращается с 0, что является кодом для "конца файла".

Ответ 4

В первый раз, когда он считает, что он вводится, второй раз он держится!

Это происходит только тогда, когда входной сигнал находится в tty. Вероятно, из-за настроек терминала, где символы буферизуются до тех пор, пока не будет введена новая строка (возврат каретки).

Ответ 5

Используя форму строки для чтения строки из файла, Python использует скрытый буфер чтения (см. http://docs.python.org/2.7/library/stdtypes.html#file-objects в функции file.next). Прежде всего, это объясняет, почему программа, которая записывает выходные данные, когда каждая строка ввода считывается, не выводит вывод, пока не нажмете CTRL-D. Во-вторых, чтобы дать пользователю некоторый контроль над буферизацией, нажатие CTRL-D сбрасывает входной буфер в код приложения. Нажатие CTRL-D, когда входной буфер пуст, рассматривается как EOF.

Связывание этого вместе отвечает на исходный вопрос. После ввода некоторого ввода первый ctrl-D (по строке сам по себе) вводит ввод в код приложения. Теперь, когда буфер пуст, второй ctrl-D действует как End-of-File (EOF).

file.readline() не проявляет этого поведения.