2010-01-29 15 views
13

Tengo el siguiente script de Python que lee números y genera un error si la entrada no es un número.¿Por qué tengo que presionar Ctrl + D dos veces para cerrar stdin?

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) 

Si consigo la entrada de la entrada estándar, tengo que presionar Ctrl + D dos veces para finalizar el programa. ¿Por qué?

sólo tiene que pulsar Ctrl + D vez cuando corro el intérprete de Python por sí mismo.

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

No obtengo ese efecto en OSX. Sin embargo, si presiono directamente después de presionar 5, (sin un retorno de carro intermedio) lo hago, e incluso 'cat' hace eso. –

+0

@Kristo: su ejemplo debe estar formateado para mostrar '' en la misma línea que '5'. Si está viendo el comportamiento que muestra su ejemplo a partir de ahora, algo está mal. –

+0

@Alok: mi ejemplo está formateado exactamente como lo escribí. Si cambio el código para usar 'sys.stdin.readlines()', entonces el primer finaliza el programa. –

Respuesta

13

En Python 3, esto se debió a a bug in Python's standard I/O library. El error fue corregido en Python 3.3.


En un terminal Unix, al presionar Ctrl + D en realidad no se cierra el estado del proceso. Pero escribir Enter o Ctrl + D hace que la llamada del sistema OS read se devuelva de inmediato. Por lo tanto:

>>> sys.stdin.read(100) 
xyzzy      (I press Enter here) 
          (I press Ctrl+D once) 
'xyzzy\n' 
>>> 

sys.stdin.read(100) se delega en sys.stdin.buffer.read, que llama al sistema read() en un bucle hasta que se acumula la cantidad solicitada completa de los datos; o el sistema read() devuelve 0 bytes; o se produce un error (docs)(source)

Al pulsar Enter después de la primera línea, el sistema read() devolvió 6 bytes. sys.stdin.buffer.read llamado read() nuevamente para tratar de obtener más información. Luego presioné Ctrl + D, lo que hizo que read() devolviera 0 bytes. En este punto, sys.stdin.buffer.read se dio por vencido y devolvió solo los 6 bytes que había recogido anteriormente.

Tenga en cuenta que el proceso todavía tiene mi terminal en stdin, y todavía puedo escribir cosas.

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

OK. Esta es la parte que se rompió cuando se formuló esta pregunta originalmente. Ahora funciona. Pero antes de Python 3.3, había un error.

El error fue un poco complicado --- básicamente el problema era que dos capas separadas estaban haciendo el mismo trabajo. BufferedReader.read() se escribió para llamar al self.raw.read() repetidamente hasta que devolvió 0 bytes. Sin embargo, el método sin formato, FileIO.read(), realizó un bucle hasta cero bytes propios.Así que la primera vez que presione Ctrl + D en un Python con este error, provocaría que FileIO.read() devolviera 6 bytes a BufferedReader.read(), lo que inmediatamente llamaría self.raw.read() nuevamente. La segunda Ctrl + D causaría que para devolver 0 bytes, y luego BufferedReader.read() finalmente saldría.

Desafortunadamente, esta explicación es mucho más larga que la anterior, pero tiene la virtud de ser correcta. Los insectos son así ...

+0

es probablemente un error: debería ser suficiente presionar 'Ctrl + D' una vez * al comienzo de una línea *] (http://stackoverflow.com/a/21261742/4279). Aunque no puedo reproducirlo (un solo 'Ctrl + D' es suficiente para terminar' sys.stdin.read() 'si se presiona Enter en ambos Python 2 y 3 - necesita presionar' Ctrl + D' dos veces solo en el medio de una línea (se establece el indicador ICANON)). – jfs

+0

@ J.F.Sebastian La rareza en este caso estaba en el lado de Python, no en la implementación del terminal del sistema operativo.Ctrl + D enviaba EOF las dos veces que se presionó, tal como dices. Pero la implementación de 'sys.stdin.read()' era un bucle simple que seguía llamando 'leer' hasta que devolvió cero bytes. –

+0

Si no está claro: lo he probado en Python 2 y 3: * single * Ctrl + D es suficiente (Ubuntu 14.04). 'read' devuelve cero bytes al comienzo de una línea. – jfs

0

La primera vez que lo considera como entrada, ¡la segunda vez es para siempre!

Esto solo ocurre cuando la entrada es desde un tty. Es probable debido a la configuración del terminal donde los caracteres se almacenan en el búfer hasta que se ingrese una nueva línea (retorno de carro).

4

Escribí una explicación al respecto en mi respuesta a esta pregunta.

How to capture Control+D signal?

En resumen, Control-D en el terminal simplemente hace que el terminal para eliminar la entrada. Esto hace que la devolución de llamada del sistema read. La primera vez que vuelve con un valor distinto de cero (si escribiste algo). La segunda vez, regresa con 0, que es el código para "fin de archivo".

+1

Eso es lo que no entiendo. El primer en mi ejemplo está en el primer carácter de una nueva línea, por lo que espero que actúe como EOF. Si cambio el código para usar 'sys.stdin.readlines()' en su lugar, el primer finaliza el programa. –

9

Lo más probable es que esto tiene que ver con Python los siguientes temas: Python

  • 5505: sys.stdin.read() no regresa después de la primera EOF en Windows, y
  • 1633941: for line in sys.stdin: no nota EOF la primera vez.
0

Usando el "para la línea en el archivo:" forma de leer las líneas de un archivo, Python utiliza un búfer de lectura anticipada oculta (ver http://docs.python.org/2.7/library/stdtypes.html#file-objects en la función file.next). En primer lugar, esto explica por qué un programa que escribe salida cuando se lee cada línea de entrada no muestra salida hasta que presione CTRL-D. En segundo lugar, para dar al usuario cierto control sobre el almacenamiento en búfer, presionar CTRL-D vacía el búfer de entrada al código de la aplicación. Presionar CTRL-D cuando el buffer de entrada está vacío se trata como EOF.

Tying this together responde a la pregunta original. Después de ingresar una entrada, la primera ctrl-D (en una línea sola) vacía la entrada al código de la aplicación. Ahora que el buffer está vacío, el segundo ctrl-D actúa como End-of-File (EOF).

file.readline() no presenta este comportamiento.

Cuestiones relacionadas