Pourquoi la touche Entrée n'envoie-t-elle pas EOL?

19

Unix / Linux EOL est LF, saut de ligne, ASCII 10, séquence d'échappement \n.

Voici un extrait de code Python pour obtenir exactement une pression de touche:

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

Lorsque j'appuie Entersur mon clavier en réponse à cet extrait, il donne \r, retour chariot, ASCII 13.

Sous Windows , Enterenvoie CR LF == 13 10. * nix n'est pas Windows; pourquoi Enterdonne 13 plutôt que 10?

chat
la source
Essayez de lire deux octets.
Michael Hampton
@MichaelHampton Non, il n'y a rien d'attente sur ce descripteur de fichier après la lecture d'un octet
cat

Réponses:

11

Bien que la réponse de Thomas Dickey soit tout à fait correcte, Stéphane Chazelas a correctement mentionné dans un commentaire à la réponse de Dickey que la conversion n'est pas figée; cela fait partie de la discipline de ligne.

En fait, la traduction est entièrement programmable.

La page de manuel man 3 termios contient essentiellement toutes les informations pertinentes. (Le lien mène au projet de pages de manuel Linux , qui mentionne les fonctionnalités qui sont uniquement Linux et celles qui sont communes à POSIX ou à d'autres systèmes; vérifiez toujours la section Conformité à sur chaque page.)

Les iflagattributs de terminal ( old_settings[0]dans le code montré dans la question en Python ) ont trois drapeaux pertinents sur tous les systèmes POSIXy:

  • INLCR: Si défini, traduire NL en CR en entrée
  • ICRNL: Si défini (et IGNCRn'est pas défini), traduire CR en NL en entrée
  • IGNCR: Ignorer CR sur l'entrée

De même, il existe également des paramètres de sortie associés ( old_settings[1]):

  • OPOST: Activer le traitement de sortie.
  • OCRNL: Mappez CR à NL en sortie.
  • ONLCR: Mappez NL sur CR en sortie. (XSI; non disponible sur tous les systèmes POSIX ou Single-Unix-Specification.)
  • ONOCR: Ignorer (ne pas sortir) CR dans la première colonne.
  • ONLRET: Ignorer (ne pas sortir) CR.

Par exemple, vous pourriez éviter de vous fier au ttymodule. L'opération "makeraw" efface simplement un ensemble d'indicateurs (et définit l' 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

bien que pour des raisons de compatibilité, vous souhaiterez peut-être vérifier si toutes ces constantes existent d'abord dans le module termios (si vous exécutez sur des systèmes non-POSIX). Vous pouvez également utiliser new_settings[6][termios.VMIN]et new_settings[6][termios.VTIME]pour définir si une lecture se bloquera s'il n'y a pas de données en attente, et combien de temps (en nombre entier de décisecondes). (Il VMINest généralement défini sur 0 et VTIMEsur 0 si les lectures doivent revenir immédiatement, ou sur un nombre positif (dixième de seconde) pendant combien de temps la lecture doit attendre au plus.)

Comme vous pouvez le voir, ce qui précède (et "makeraw" en général) désactive toutes les traductions en entrée, ce qui explique le comportement du chat:

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

Pour obtenir un comportement normal, il suffit d'omettre les lignes effaçant ces trois lignes, et la traduction d'entrée est inchangée même lorsqu'elle est "brute".

La new_settings[1] = new_settings[1] & ~termios.OPOSTligne désactive tout le traitement de sortie, indépendamment de ce que disent les autres drapeaux de sortie. Vous pouvez simplement l'omettre pour garder le traitement de sortie intact. Cela maintient la sortie "normale" même en mode brut. (Cela n'affecte pas si l'entrée est automatiquement répercutée ou non; cela est contrôlé par le ECHOcflag in new_settings[3].)

Enfin, lorsque de nouveaux attributs sont définis, l'appel réussit si l' un des nouveaux paramètres a été défini. Si les paramètres sont sensibles - par exemple, si vous demandez un mot de passe sur la ligne de commande -, vous devez obtenir les nouveaux paramètres et vérifier que les indicateurs importants sont correctement définis / désactivés, pour être sûr.

Si vous souhaitez voir vos paramètres de terminal actuels, exécutez

stty -a

Les drapeaux d'entrée sont généralement sur la quatrième ligne et les drapeaux de sortie sur la cinquième ligne, avec un -précédant le nom du drapeau si le drapeau n'est pas défini. Par exemple, la sortie pourrait être

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

Sur les pseudoterminaux et les périphériques USB TTY, le débit en bauds n'est pas pertinent.

Si vous écrivez des scripts Bash qui souhaitent lire par exemple des mots de passe, considérez l'idiome suivant:

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

Le EXITtrap est exécuté à chaque sortie du shell. Le stty -glit les paramètres actuels du terminal au début du script, afin que les paramètres actuels soient restaurés automatiquement à la sortie du script. Vous pouvez même interrompre le script avec Ctrl+ C, et cela fera la bonne chose. (Dans certains cas d'angle avec des signaux, j'ai constaté que le terminal est parfois bloqué avec les paramètres bruts / non canoniques (en exigeant un pour taper reset+ Enteraveuglément sur le terminal), mais courir stty saneavant de restaurer les paramètres d'origine réels a guéri cela à chaque fois pour C'est pourquoi c'est là, une sorte de sécurité supplémentaire.)

Vous pouvez lire les lignes d'entrée (sans écho sur le terminal) à l'aide de readbash intégré, ou même lire l'entrée caractère par caractère à l'aide

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

Si vous ne définissez pas IFSsur ASCII NUL, la fonction readintégrée consommera les séparateurs, ce qui csera vide. Piège pour jeunes joueurs.

Animal nominal
la source
1
Oh, pour l'amour des dieux, rien n'est jamais simple :(
cat
J'accepte cette réponse parce qu'elle est très utile pour moi en tant que développeur Python, même si l'autre est génial
chat
2
@cat: Bien que cela puisse vous être très utile, je dirais quand même que la réponse de Thomas Dickey est plus correcte . Je préfère que vous acceptiez cela à la place.
Animal nominal
4
Bien que votre volonté de renoncer à votre représentant +15 vous le reconnaisse, @cat a tout à fait raison. Le fait qu'une réponse soit acceptée ou non n'indique pas qu'elle est la «plus correcte» des réponses publiées. Cela signifie seulement que c'est celui que l'OP a préféré pour toutes les raisons personnelles. Le «plus correct» est généralement le plus voté. Accepter une réponse est une préférence personnelle, si le PO préfère la vôtre, il n'y a aucune raison de ne pas l'accepter.
terdon
1
@terdon: D'accord, je suis corrigé, alors.
Animal nominal
30

Essentiellement "parce que c'est fait ainsi depuis les machines à écrire manuelles". Vraiment.

Une machine à écrire manuelle avait un chariot sur lequel le papier était alimenté, et elle avançait au fur et à mesure que vous tapiez (chargement d'un ressort), et avait un levier ou une clé qui libérerait le chariot, laissant le ressort ramener le chariot vers la marge gauche.

Au fur et à mesure de l'introduction de la saisie électronique des données (téléscripteur, etc.), ils l'ont fait avancer. Ainsi, la Enterclé sur de nombreux terminaux serait étiquetée Return.

Les sauts de ligne se sont produits (dans le processus manuel) après le retour du chariot à la marge gauche. Encore une fois, les appareils électroniques imitaient les appareils manuels, ce qui faisait une line-feedopération distincte .

Les deux opérations sont codées (pour permettre au télétype d'être plus qu'un périphérique autonome créant un type de papier), nous avons donc CR(retour chariot) et LF(saut de ligne). Cette image de ASR 33 Teletype Information montre le clavier, avec Returnsur le côté droit et Line-Feedjuste à gauche. Être à droite , c'était la clé principale:

entrez la description de l'image ici

Unix est arrivé plus tard. Ses développeurs aimaient raccourcir les choses (regardez toutes les abréviations, même creatpour "créer"). Confrontés à un processus éventuellement en deux parties, ils ont décidé que les sauts de ligne n'avaient de sens que s'ils étaient précédés de retours chariot. Ils ont donc supprimé les retours chariot explicites des fichiers et traduit la Returnclé du terminal pour envoyer le saut de ligne correspondant. Pour éviter toute confusion, ils ont qualifié le saut de ligne de "saut de ligne".

Lors de l'écriture de texte sur le terminal, Unix se traduit dans l'autre sens: un saut de ligne devient retour chariot / saut de ligne.

(C'est-à-dire "normalement": ce qu'on appelle le "mode cuit", contrairement au mode "brut" où aucune traduction n'est effectuée).

Sommaire:

  • retour chariot / saut de ligne est la séquence 13 10
  • l' appareil en envoie 13 (depuis "pour toujours" selon vos termes)
  • Les systèmes de type Unix le changent en 13 10
  • D'autres systèmes ne stockent pas nécessairement seulement 10 (Windows accepte largement 10 ou 13 10, selon l'importance de la compatibilité).
Thomas Dickey
la source
1
J'ai cherché une belle image pour montrer les leviers d'une machine à écrire manuelle, mais je n'ai trouvé que des images basse résolution.
Thomas Dickey
3
Si vous deviez taper sur l'un d'eux, vous abrégeriez tout aussi!
Michael Hampton
3
En ce qui concerne la partie historique: les machines à écrire manuelles que j'ai utilisées dans mon utilisation, similaires à celle-ci, n'avaient qu'un seul levier. Lorsque vous l'avez tiré, il a d'abord lancé le rouleau (saut de ligne), puis il a simplement tiré le chariot. Et c'est cette traction qui a chargé le ressort. Chaque lettre tapée ou appuyée sur une languette relâcherait quelque peu le ressort, ramenant le chariot à la position "déchargée", qui était à la fin de la ligne, pas à son début.
RealSkeptic
2
En entrée, CR est traduit (par la discipline de ligne tty) en LF, pas CR LF. C'est la sortie (y compris l'écho de l'entrée) qui LF est traduite en CR LF. Lorsque vous tapez foo<Return>en mode cuit, l'application lit foo\net foo\r\nest renvoyée par la discipline de ligne pour écho au terminal.
Stéphane Chazelas