¿Por qué la tecla Enter no envía EOL?

19

Unix / Linux EOL es LF, salto de línea, ASCII 10, secuencia de escape \n.

Aquí hay un fragmento de Python para obtener exactamente una pulsación de tecla:

import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
    tty.setraw(sys.stdin.fileno())
    ch = sys.stdin.read(1)
finally:
    termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return ch

Cuando presiono Entermi teclado en respuesta a este fragmento \r, aparece el retorno de carro ASCII 13.

En Windows , Enterenvía CR LF == 13 10. * nix no es Windows; ¿Por qué Enterda 13 en lugar de 10?

gato
fuente
Intenta leer dos bytes.
Michael Hampton
@MichaelHampton No, no hay nada esperando en ese descriptor de archivo después de leer un byte
gato

Respuestas:

11

Si bien la respuesta de Thomas Dickey es bastante correcta, Stéphane Chazelas mencionó correctamente en un comentario a la respuesta de Dickey que la conversión no está escrita en piedra; Es parte de la disciplina de línea.

De hecho, la traducción es completamente programable.

La página del manual man 3 termios contiene básicamente toda la información pertinente. (El enlace lleva al proyecto de páginas de manual de Linux , que menciona qué características son exclusivas de Linux y cuáles son comunes a POSIX u otros sistemas; siempre revise la sección Conforme a en cada página allí).

Los iflagatributos del terminal ( old_settings[0]en el código que se muestra en la pregunta en Python ) tienen tres indicadores relevantes en todos los sistemas POSIXy:

  • INLCR: Si está configurado, traduzca NL a CR en la entrada
  • ICRNL: Si está configurado (y IGNCRno está configurado), traduzca CR a NL en la entrada
  • IGNCR: Ignorar CR en entrada

Del mismo modo, también hay configuraciones de salida relacionadas ( old_settings[1]):

  • OPOST: Habilita el procesamiento de salida.
  • OCRNL: Asigna CR a NL en la salida.
  • ONLCR: Asigna NL a CR en la salida. (XSI; no disponible en todos los sistemas POSIX o Single-Unix-Specification).
  • ONOCR: Omitir (no generar) CR en la primera columna.
  • ONLRET: Saltar (no emitir) CR.

Por ejemplo, podría evitar depender del ttymódulo. La operación "makeraw" simplemente borra un conjunto de banderas (y establece el CS8oflag):

import sys
import termios

fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
ch = None

try:
    new_settings = termios.tcgetattr(fd)
    new_settings[0] = new_settings[0] & ~termios.IGNBRK
    new_settings[0] = new_settings[0] & ~termios.BRKINT
    new_settings[0] = new_settings[0] & ~termios.PARMRK
    new_settings[0] = new_settings[0] & ~termios.ISTRIP
    new_settings[0] = new_settings[0] & ~termios.INLCR
    new_settings[0] = new_settings[0] & ~termios.IGNCR
    new_settings[0] = new_settings[0] & ~termios.ICRNL
    new_settings[0] = new_settings[0] & ~termios.IXON
    new_settings[1] = new_settings[1] & ~termios.OPOST
    new_settings[2] = new_settings[2] & ~termios.CSIZE
    new_settings[2] = new_settings[2] | termios.CS8
    new_settings[2] = new_settings[2] & ~termios.PARENB
    new_settings[3] = new_settings[3] & ~termios.ECHO
    new_settings[3] = new_settings[3] & ~termios.ECHONL
    new_settings[3] = new_settings[3] & ~termios.ICANON
    new_settings[3] = new_settings[3] & ~termios.ISIG
    new_settings[3] = new_settings[3] & ~termios.IEXTEN
    termios.tcsetattr(fd, termios.TCSANOW, new_settings)
finally:
    termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

return ch

aunque por razones de compatibilidad, es posible que desee verificar si todas esas constantes existen primero en el módulo termios (si se ejecuta en sistemas que no son POSIX). También puede usar new_settings[6][termios.VMIN]y new_settings[6][termios.VTIME]establecer si una lectura se bloqueará si no hay datos pendientes y cuánto tiempo (en un número entero de decisegundos). (Por VMINlo general, se establece en 0 y VTIMEen 0 si las lecturas deben regresar de inmediato, o en un número positivo (décima de segundos) cuánto tiempo debe esperar la lectura como máximo).

Como puede ver, lo anterior (y "makeraw" en general) desactiva toda traducción en la entrada, lo que explica el comportamiento que está viendo cat:

    new_settings[0] = new_settings[0] & ~termios.INLCR
    new_settings[0] = new_settings[0] & ~termios.ICRNL
    new_settings[0] = new_settings[0] & ~termios.IGNCR

Para obtener un comportamiento normal, simplemente omita las líneas que borran esas tres líneas, y la traducción de entrada no cambia incluso cuando está "sin procesar".

La new_settings[1] = new_settings[1] & ~termios.OPOSTlínea deshabilita todo el procesamiento de salida, independientemente de lo que digan los otros indicadores de salida. Puede omitirlo para mantener intacto el procesamiento de salida. Esto mantiene la salida "normal" incluso en modo sin procesar. (No afecta si la entrada se repite automáticamente o no; eso es controlado por el ECHOcflag in new_settings[3].)

Finalmente, cuando se establecen nuevos atributos, la llamada será exitosa si se estableció alguna de las nuevas configuraciones. Si la configuración es delicada, por ejemplo, si solicita una contraseña en la línea de comando, debe obtener la nueva configuración y verificar que los indicadores importantes estén correctamente activados / desactivados, para estar seguro.

Si desea ver la configuración actual de su terminal, ejecute

stty -a

Los indicadores de entrada suelen estar en la cuarta línea, y los indicadores de salida en la quinta línea, con un -nombre de indicador anterior si el indicador no está establecido. Por ejemplo, la salida podría ser

speed 38400 baud; rows 58; columns 205; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = M-^?; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 hupcl -cstopb cread -clocal -crtscts
-ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke

En pseudoterminales y dispositivos USB TTY, la velocidad de transmisión es irrelevante.

Si escribe scripts de Bash que desean leer, por ejemplo, contraseñas, considere el siguiente modismo:

#!/bin/bash
trap 'stty sane ; stty '"$(stty -g)" EXIT
stty -echo -echonl -imaxbel -isig -icanon min 1 time 0

La EXITtrampa se ejecuta cada vez que sale el shell. El stty -glee la configuración actual del terminal en el inicio de la secuencia de comandos, por lo que los ajustes actuales se restauran cuando las salidas de guión, de forma automática. Incluso puede interrumpir el script con Ctrl+ C, y hará lo correcto. (En algunos casos de esquina con señales, descubrí que el terminal a veces se atasca con la configuración sin formato / no canónica (que requiere que uno escriba reset+Enter ciegas en el terminal), pero la ejecución stty saneantes de restaurar la configuración original real lo ha curado todo el tiempo yo. Entonces es por eso que está ahí; una especie de seguridad adicional).

Puede leer las líneas de entrada (sin eco al terminal) utilizando readbash incorporado, o incluso leer la entrada carácter por carácter utilizando

IFS=$'\0'
input=""
while read -N 1 c ; do
    [[ "$c" == "" || "$c" == $'\n' || "$c" == $'\r' ]] && break
    input="$input$c"
done

Si no establece IFSASCII NUL, el readincorporado consumirá los separadores, por lo que cestará vacío. Trampa para jugadores jóvenes.

Animal nominal
fuente
1
Oh, por el amor de Dios, nada es nunca simple :(
gato
Estoy aceptando esta respuesta porque es muy útil para mí como desarrollador de Python, a pesar de que la otra es excelente
gato
2
@cat: Si bien esto puede ser más útil para usted, todavía diría que la respuesta de Thomas Dickey es más correcta . Prefiero que aceptes eso en su lugar.
Nominal Animal
44
Si bien su disposición a renunciar a su representante de +15 le da crédito, @cat tiene toda la razón. Si una respuesta es aceptada o no, no es indicación de que sea la "más correcta" de las respuestas publicadas. Solo significa que ese es el OP preferido por cualquier razón personal. El "más correcto" suele ser el más votado. Aceptar una respuesta depende de la preferencia personal, si el OP prefiere la suya, no hay razón para no aceptarla.
terdon
1
@terdon: Bien, estoy corregido, entonces.
Nominal Animal
30

Esencialmente "porque se ha hecho así desde máquinas de escribir manuales". De Verdad.

Una máquina de escribir manual tenía un carro en el que se alimentaba el papel, y se movía hacia adelante mientras escribía (cargando un resorte), y tenía una palanca o llave que soltaba el carro, permitiendo que el resorte volviera el carro al margen izquierdo.

Cuando se introdujo el ingreso electrónico de datos (teletipo, etc.), lo llevaron adelante. Entonces la Enterclave en muchas terminales estaría etiquetada Return.

Los avances de línea ocurrieron (en el proceso manual) después de devolver el carro al margen izquierdo. Una vez más, los dispositivos electrónicos imitaron a los dispositivos manuales, haciendo una line-feedoperación separada .

Ambas operaciones están codificadas (para permitir que el teletipo sea más que un dispositivo independiente que crea un tipo de papel), por lo que tenemos CR(retorno de carro) y LF(avance de línea). Esta imagen de la información del teletipo ASR 33 muestra el teclado, con Returnel lado derecho y Line-Feedjusto a la izquierda. Estar a la derecha , era la clave principal:

ingrese la descripción de la imagen aquí

Unix llegó más tarde. A sus desarrolladores les gustaba acortar las cosas (mira todas las abreviaturas, incluso creatpara "crear"). Ante un proceso posiblemente de dos partes, decidieron que los avances de línea solo tenían sentido si iban precedidos de retornos de carro. Así que descartaron los retornos de carro explícitos de los archivos y tradujeron el terminalReturn clave para enviar el avance de línea correspondiente. Solo para evitar confusiones, se refirieron al avance de línea como "nueva línea".

Al escribir texto en el terminal, Unix traduce en la otra dirección: un avance de línea se convierte en retorno de carro / avance de línea.

(Es decir, "normalmente": llamado "modo cocinado", en contraste con el modo "sin procesar" donde no se realiza la traducción).

Resumen:

  • retorno de carro / avance de línea es la secuencia 13 10
  • el dispositivo envía 13 (desde "para siempre" en sus términos)
  • Los sistemas tipo Unix cambian eso a 13 10
  • Otros sistemas no almacenan necesariamente solo 10 (Windows acepta en gran medida solo 10 o 13 10, dependiendo de la importancia de la compatibilidad).
Thomas Dickey
fuente
1
Busqué una buena imagen para mostrar las palancas de una máquina de escribir manual, pero solo encontré imágenes de baja resolución.
Thomas Dickey
3
Si tuviera que escribir en uno de esos, ¡también abreviaría todo!
Michael Hampton
3
Con respecto a la parte de la historia: las máquinas de escribir manuales que utilicé en mi uso, similares a esta, solo tenían una palanca. Cuando lo jalaste, primero hizo girar el rodillo (avance de línea) y luego simplemente tiraba del carro. Y fue este tirón el que cargó la primavera. Cada letra escrita, o presionada la lengüeta, liberaría un poco el resorte, moviendo el carro nuevamente a la posición "descargada", que estaba al final de la línea, no al comienzo.
RealSkeptic
2
En la entrada, CR se traduce (por la disciplina de línea tty) a LF, no CR LF. Está en la salida (incluido el eco de la entrada) a la que LF se traduce CR LF. Cuando escribe foo<Return>en modo cocinado, la aplicación lee foo\ny foo\r\nla disciplina de línea la envía de vuelta para que se repita en el terminal.
Stéphane Chazelas