Perché il tasto Invio non invia EOL?


19

Unix / Linux EOL è LF, linefeed, ASCII 10, sequenza di escape \n.

Ecco uno snippet Python per ottenere esattamente un tasto:

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

Quando premo Entersulla tastiera in risposta a questo frammento, esso dà \r, ritorno a capo, ASCII 13.

Su Windows , Enterinvia CR LF == 13 10. * nix non è Windows; perché Enterdà 13 anziché 10?


Prova a leggere due byte.
Michael Hampton,

@MichaelHampton No, non c'è nulla in attesa su quel descrittore di file dopo la lettura di un byte
cat

Risposte:


11

Mentre la risposta di Thomas Dickey è abbastanza corretta, Stéphane Chazelas ha giustamente menzionato in un commento alla risposta di Dickey che la conversione non è messa in pietra; fa parte della disciplina di linea.

In effetti, la traduzione è completamente programmabile.

La pagina man di man 3 termios contiene sostanzialmente tutte le informazioni pertinenti. (Il collegamento porta al progetto delle pagine man di Linux , che menziona quali funzionalità sono solo Linux e quali sono comuni a POSIX o ad altri sistemi; controlla sempre la sezione Conforming to su ciascuna pagina lì.)

Gli iflagattributi del terminale ( old_settings[0]nel codice mostrato nella domanda in Python ) hanno tre flag rilevanti su tutti i sistemi POSIXy:

  • INLCR: Se impostato, traduci NL in CR in input
  • ICRNL: Se impostato (e IGNCRnon impostato), traduci CR in NL in input
  • IGNCR: Ignora CR in input

Allo stesso modo, ci sono anche impostazioni di output correlate ( old_settings[1]):

  • OPOST: Abilita l'elaborazione dell'output.
  • OCRNL: Mappa CR su NL in uscita.
  • ONLCR: Mappa NL a CR in uscita. (XSI; non disponibile in tutti i sistemi POSIX o Single-Unix-Specification.)
  • ONOCR: Salta (non emette) CR nella prima colonna.
  • ONLRET: Salta (non emette) CR.

Ad esempio, potresti evitare di fare affidamento sul ttymodulo. L'operazione "makeraw" cancella solo una serie di flag (e imposta la 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

anche se per motivi di compatibilità, potresti voler verificare se tutte quelle costanti esistono prima nel modulo termios (se si esegue su sistemi non POSIX). Puoi anche usare new_settings[6][termios.VMIN]e new_settings[6][termios.VTIME]impostare se una lettura si bloccherà se non ci sono dati in sospeso e per quanto tempo (in numero intero di decisecondi). (In genere VMINè impostato su 0 e VTIMEsu 0 se le letture devono tornare immediatamente o su un numero positivo (decimo di secondo) per quanto tempo la lettura dovrebbe attendere al massimo.)

Come puoi vedere, quanto sopra (e "makeraw" in generale) disabilita tutte le traduzioni sull'input, il che spiega il comportamento che sta osservando il gatto:

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

Per ottenere un comportamento normale, è sufficiente omettere le righe che cancellano quelle tre righe e la traduzione di input rimane invariata anche se "non elaborata".

La new_settings[1] = new_settings[1] & ~termios.OPOSTlinea disabilita tutta l'elaborazione dell'output, indipendentemente da ciò che dicono gli altri flag di output. Puoi semplicemente ometterlo per mantenere intatta l'elaborazione dell'output. Ciò mantiene l'output "normale" anche in modalità raw. (Non influisce sul fatto che l'ingresso venga ripetuto automaticamente o meno; ciò è controllato dal ECHOcflag in new_settings[3].)

Infine, quando vengono impostati nuovi attributi, la chiamata avrà esito positivo se sono state impostate una delle nuove impostazioni. Se le impostazioni sono sensibili, ad esempio se si richiede una password dalla riga di comando, è necessario ottenere le nuove impostazioni e verificare che i flag importanti siano impostati / disinseriti correttamente, per essere sicuri.

Se si desidera visualizzare le impostazioni correnti del terminale, eseguire

stty -a

I flag di input sono in genere sulla quarta riga e i flag di output sulla quinta riga, con un -precedente il nome del flag se il flag non è impostato. Ad esempio, l'output potrebbe essere

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

Su pseudoterminali e dispositivi USB TTY, la velocità di trasmissione è irrilevante.

Se scrivi script Bash che desiderano leggere ad esempio password, considera il seguente linguaggio:

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

La EXITtrap viene eseguita ogni volta che la shell esce. La stty -glegge le impostazioni correnti del terminale all'inizio dello script, in modo che le impostazioni correnti vengono ripristinate quando lo script si chiude, automaticamente. Puoi anche interrompere lo script con Ctrl+ C, e farà la cosa giusta. (In alcuni casi angolari con segnali, ho scoperto che il terminale a volte si blocca con le impostazioni raw / non canoniche (che richiedono uno per digitare reset+ Enterciecamente al terminale), ma l'esecuzione stty saneprima di ripristinare le impostazioni originali effettive ha curato ogni volta Io. Ecco perché è lì; una sorta di maggiore sicurezza.)

Puoi leggere le righe di input (non copiate sul terminale) usando readbash incorporato, o persino leggere l'input carattere per carattere usando

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

Se non si imposta IFSASCII NUL, il readbuilt-in consumerà i separatori, quindi csarà vuoto. Trappola per giovani giocatori.


1
Oh, per l'amor di Dio, niente è mai semplice :(
gatto,

Sto accettando questa risposta perché mi è molto utile come sviluppatore di Python, anche se l'altro è fantastico
gatto

2
@cat: Sebbene ciò possa esserti di grande aiuto, direi comunque che la risposta di Thomas Dickey è più corretta . Preferirei che lo accettassi invece.
Animale nominale

4
Mentre la tua disponibilità a rinunciare al tuo rappresentante +15 ti dà credito, @cat ha ragione. L'accettazione o meno di una risposta non indica che sia la "più corretta" delle risposte postate. Significa solo che l'OP ha preferito per qualsiasi motivo personale. Il "più corretto" è in genere il più altamente votato. Accettare una risposta dipende dalle preferenze personali, se l'OP preferisce la tua, non c'è motivo di non accettarla.
terdon

1
@terdon: va bene, allora sono corretto.
Animale nominale

30

In sostanza "perché è stato fatto in questo modo dalle macchine da scrivere manuali". Veramente.

Una macchina da scrivere manuale aveva un carrello su cui veniva alimentata la carta, che si spostava in avanti durante la digitazione (caricamento di una molla) e aveva una leva o un tasto che rilasciava il carrello, lasciando che la molla riportasse il carrello sul margine sinistro.

Quando furono introdotti dati elettronici (teletype, ecc.), Li portarono avanti. Quindi la Enterchiave su molti terminali verrebbe etichettata Return.

Gli avanzamenti di riga sono avvenuti (nel processo manuale) dopo aver restituito il carrello al margine sinistro. Ancora una volta, i dispositivi elettronici imitavano i dispositivi manuali, effettuando un'operazione separata line-feed.

Entrambe le operazioni sono codificate (per consentire al teletipo di essere più di un dispositivo autonomo che crea un tipo di carta), quindi abbiamo CR(ritorno a capo) e LF(avanzamento riga). Questa immagine da ASR 33 Teletype Information mostra la tastiera, con Returnsul lato destro e Line-Feedappena a sinistra. Essere sulla destra , era la chiave principale:

inserisci qui la descrizione dell'immagine

Unix arrivò più tardi. Ai suoi sviluppatori piaceva accorciare le cose (guarda tutte le abbreviazioni, anche creatper "creare"). Di fronte a un possibile processo in due parti, decisero che gli avanzamenti di riga avevano senso solo se erano preceduti da ritorni a capo. Quindi hanno eliminato i ritorni a capo espliciti dai file e hanno tradotto la Returnchiave del terminale per inviare il corrispondente avanzamento di riga. Solo per evitare confusione, si riferivano al feed di riga come "newline".

Quando si scrive del testo sul terminale, Unix si traduce nella direzione opposta: un avanzamento riga diventa ritorno a capo / avanzamento riga.

(Cioè, "normalmente": la cosiddetta "modalità cotta", in contrasto con la modalità "grezza" in cui non viene eseguita alcuna traduzione).

Sommario:

  • ritorno a capo / avanzamento riga è la sequenza 13 10
  • il dispositivo invia 13 (da "per sempre" nei tuoi termini)
  • I sistemi simili a Unix lo cambiano in 13 10
  • Altri sistemi non memorizzano necessariamente solo 10 (Windows accetta in gran parte solo 10 o 13 10, a seconda dell'importanza della compatibilità).

1
Ho cercato una bella foto per mostrare le leve di una macchina da scrivere manuale, ma ho trovato solo immagini a bassa risoluzione.
Thomas Dickey,

3
Se dovessi scrivere su uno di questi, abbrevieresti anche tutto!
Michael Hampton,

3
Per quanto riguarda la parte storica: le macchine da scrivere manuali che ho usato nel mio uso, simili a questa avevano solo una leva. Quando l'hai tirato, per prima cosa ha fatto girare il rullo (avanzamento di linea) e poi ha semplicemente trascinato il carrello. Ed è stata questa attrazione che ha caricato la molla. Ogni lettera digitata, o la pressione di una linguetta, rilasciava un po 'la molla, spostando il carrello nella posizione "scarica", che era alla fine della linea, non all'inizio.
RealSkeptic,

2
All'input, CR viene tradotto (dalla disciplina tty line) in LF, non in CR LF. È in output (incluso l'eco dell'input) che LF viene tradotto CR LF. Quando si digita foo<Return>in modalità cotta, l'applicazione legge foo\ne foo\r\nviene inviata dalla disciplina di linea per l'eco al terminale.
Stéphane Chazelas,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.