Почему мне нужно дважды набирать ctrl-d?

Для моего собственного развлечения я приготовил python script, который позволяет мне использовать python для bash однострочных; Поставить выражение генератора питона; и script выполняет итерацию по ней. Здесь script:

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 в любое время закончится script. Где разрыв в моем понимании?

EDIT: я обновил интерактивный пример, чтобы показать, что я не вижу цитирования wim в его ответе и еще несколько примеров.

$ 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'
$ 

Ответы

Ответ 1

Ctrl-D распознается не обязательно как EOF, а как "endinate current read() call".

Если у вас есть пустая строка (или просто нажата Ctrl-D) и нажмите Ctrl-D, ваш read() немедленно завершает работу и возвращает 0 считанных байтов. И это знак для EOF.

Если у вас есть данные в строке и нажмите Ctrl-D, ваш read() завершается тем, что было напечатано, конечно, без завершающей строки новой строки ('\n').

Итак, если у вас есть входные данные, вы нажимаете Ctrl-D дважды непустой строки или один раз на пустой, т.е. с Enter раньше.

Все это выполняется для обычного интерфейса ОС, доступного из Python через os.read().

Файловые объекты Python, а также итераторы файлов, обрабатывают первый EOF, распознаваемый как завершение для текущего вызова read(), поскольку они полагают, что больше ничего нет. Следующий read() вызов снова пытается и нуждается в другом Ctrl-D, чтобы действительно вернуть 0 байт. Причина в том, что файловый объект read() всегда пытается вернуть столько байтов, сколько запрошено, и пытается заполнить, если OS read() возвращает меньше запрошенных.

В отличие от file.readline(), iter(file) использует внутренние функции read() для чтения и, следовательно, всегда имеет это особое требование дополнительного Ctrl-D.

Я всегда использую iter(file.readline, '') для чтения по строке из файла.

Ответ 2

Ctrl+D распознается терминальным устройством, терминал отвечает на него, генерируя конец файла. Возможно, это поможет, из Википедии (внимание мое):

В UNIX и AmigaDOS перевод нажатия клавиши в EOF выполняется драйвером терминала, поэтому программе не нужно различать терминалы от других входных файлов. По умолчанию драйвер преобразует символ Control-D в начале строки в индикатор конца файла. Чтобы вставить фактический символ Control-D (ASCII 04) во входной поток, пользователь предшествует ему символом команды "quote" (обычно Control-V, хотя в некоторых системах вы достигаете этого эффекта, набрав Control-D дважды).

Ответ 3

Я не могу точно сказать, почему дополнительный CTRL + D (другой ответ очень хорошо справляется с этим), но это сделает так, чтобы ввод печатался только после одного CTRL+D, но вы все еще необходимо CTRL+D выполнить второй раз, чтобы выйти из script

#!/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,

Вывод:

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

Edit:

eval возвращает генератор в этом случае, поэтому возможно, что первый EOF (CTRL + D) заканчивает чтение sys.stdin, а второй останавливает генератор, создаваемый eval.

Generator - функция, которая возвращает итератор. Он выглядит как обычная функция, за исключением того, что в нем содержатся операторы yield для создания ряда значений, используемых в for-loop или которые могут быть получены по одному с помощью следующей функции(). Каждый выход временно приостанавливает обработку, запоминая состояние выполнения местоположения (включая локальные переменные и ожидающие попытки-заявления). Когда генератор возобновляется, он подбирает место, где он остался (в отличие от функций, которые начинаются с каждого вызова).

Ссылка на класс генератора (раздел 9.10)