¿Por qué tengo que escribir ctrl-d dos veces?

Para mi propia diversión, he preparado un script de python que me permite usar python para bash one-liners; Proporcionar una expresión de generador de python y el guión itera sobre él. Aquí está el guión:

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)) 

Y aquí es cómo puedes usarlo.

 $ groups | python pype.py '(l.upper() for l in sys.stdin)' DBORNSIDE $ 

Para el uso previsto, funciona perfectamente!

Pero cuando no lo alimento con una tubería y simplemente lo invoco directamente, por ejemplo: [énfasis agregado para mostrar lo que escribo]

 $ python pype.py '("% r \ n"% (l,) para l en sys.stdin)'
 foo entrar 
  barra de entrar 
  baz entrar 
  Ctrl D Ctrl D 'foo \ n'
 'granero'
 'baz \ n'
 PS 

Para dejar de aceptar entradas y generar cualquier salida, tengo que escribir IngresarCtrl DCtrl D o Ctrl DCtrl DCtrl D. Esto viola mis expectativas, que cada línea debe procesarse como se ingresó, y que al presionar Ctrl D en cualquier momento terminará la secuencia de comandos. ¿Dónde está la brecha en mi comprensión?

EDITAR: He actualizado el ejemplo interactivo para mostrar que no veo las citas que wim describe en su respuesta, y también algunos ejemplos más.

 $ python pype.py '("% r \ n"% (l,) para l en sys.stdin)'
 foo Ctrl D Ctrl D bar Entrar 
  Ctrl D Ctrl D 'foobar \ n'
 $ python pype.py '("% r \ n"% (l,) para l en sys.stdin)'
 foo Ctrl V Ctrl D ^ D barra Entrar 
  Ctrl D Ctrl D 'foo \ x04bar \ n'
 PS 

Ctrl-D se reconoce no necesariamente como EOF, sino como “terminar la llamada de read() actual”.

Si tiene una línea vacía (o simplemente presiona Ctrl-D ) y presiona Ctrl-D , su read() termina inmediatamente y devuelve 0 bytes de lectura. Y esta es una señal para EOF.

Si tiene datos en una línea y presiona Ctrl-D , su read() termina con lo que haya escrito, por supuesto, sin una nueva línea de terminación ( '\n' ).

Entonces, si tiene datos de entrada, presiona Ctrl-D dos veces de una línea no vacía o una vez en una vacía, es decir, con Enter antes.

Todo esto es válido para la interfaz normal del sistema operativo, accesible desde Python a través de os.read() .

Los objetos de archivo de Python, y también los iteradores de archivos, tratan el primer EOF reconocido como la terminación de la llamada de read() actual, ya que suponen que ya no hay nada. Una próxima llamada read() intenta nuevamente y necesita otro Ctrl-D para devolver realmente 0 bytes. El motivo es que un objeto de archivo read() siempre intenta devolver tantos bytes como se solicite e intenta rellenar si un sistema operativo read() devuelve menos de lo solicitado.

Al contrario de file.readline() , iter(file) usa las funciones internas de read() para leer y, por lo tanto, siempre tiene este requisito especial de Ctrl-D adicional.

Siempre uso iter(file.readline, '') para leer en línea desde un archivo.

Ctrl + D es reconocido por el dispositivo terminal, el terminal responde a él generando un final de archivo. Quizás esto ayude, desde Wikipedia (énfasis mío):

En UNIX y AmigaDOS, la conversión de la pulsación de tecla a EOF es realizada por el controlador del terminal, por lo que un progtwig no necesita distinguir los terminales de otros archivos de entrada. De forma predeterminada, el controlador convierte un carácter Control-D al comienzo de una línea en un indicador de fin de archivo. Para insertar un carácter real de Control-D (ASCII 04) en el flujo de entrada, el usuario lo precede con un carácter de comando “citar” (generalmente Control-V, aunque en algunos sistemas se logra este efecto al teclear Control-D dos veces ).

No puedo decir exactamente por qué el CTRL + D adicional (la otra respuesta hace un muy buen trabajo de eso), pero esto hará que la entrada se imprima después de un solo CTRL + D , pero aún necesita CTRL + D una segunda vez para salir del 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, 

Salida:

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

Editar:

eval está devolviendo un generador en este caso, por lo que es posible que el primer EOF (CTRL + D) finalice la lectura de sys.stdin, y el segundo para el generador que eval .

Generador – Una función que devuelve un iterador. Se parece a una función normal, excepto que contiene declaraciones de rendimiento para producir una serie de valores utilizables en un bucle for o que se pueden recuperar de uno en uno con la función next (). Cada rendimiento suspende temporalmente el procesamiento, recordando el estado de ejecución de la ubicación (incluidas las variables locales y las declaraciones de prueba pendientes). Cuando se reanuda el generador, retoma el lugar donde lo dejó (en contraste con las funciones que comienzan de nuevo en cada invocación).

Referencia de la clase del generador (sección 9.10)