Warum sendet die Eingabetaste keine EOL?

19

Unix / Linux EOL ist LF, Zeilenvorschub, ASCII 10, Escape-Sequenz \n.

Hier ist ein Python-Snippet, um genau einen Tastendruck zu erhalten:

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

Wenn ich als EnterAntwort auf dieses Snippet auf meiner Tastatur drücke , gibt es \rWagenrücklauf, ASCII 13.

Unter Windows wird Entergesendet CR LF == 13 10. * nix ist nicht Windows; Warum gibt Enteres 13 statt 10?

Katze
quelle
Versuchen Sie, zwei Bytes zu lesen .
Michael Hampton
@MichaelHampton Nein, auf diesen Dateideskriptor wartet nichts, nachdem ein Byte gelesen wurde
cat

Antworten:

11

Während die Antwort von Thomas Dickey ganz richtig ist, hat Stéphane Chazelas in einem Kommentar zu Dickeys Antwort richtig erwähnt, dass die Bekehrung nicht in Stein gemeißelt ist; Es ist Teil der Liniendisziplin.

Tatsächlich ist die Übersetzung vollständig programmierbar.

Die man 3 termios- Manpage enthält grundsätzlich alle relevanten Informationen. (Der Link führt zu einem Linux-Handbuchprojekt , in dem angegeben ist, welche Funktionen nur für Linux gelten und welche für POSIX- oder andere Systeme gleich sind. Überprüfen Sie dort immer den Abschnitt Übereinstimmung mit auf jeder Seite.)

Die iflagTerminalattribute ( old_settings[0]in dem in der Frage in Python gezeigten Code ) weisen auf allen POSIXy-Systemen drei relevante Flags auf:

  • INLCR: Falls gesetzt, NL bei der Eingabe in CR übersetzen
  • ICRNL: Wenn gesetzt (und IGNCRnicht gesetzt), wird CR bei der Eingabe in NL übersetzt
  • IGNCR: CR bei der Eingabe ignorieren

Ebenso gibt es auch verwandte Ausgabeeinstellungen ( old_settings[1]):

  • OPOST: Aktiviert die Ausgabeverarbeitung.
  • OCRNL: CR auf NL bei der Ausgabe abbilden.
  • ONLCR: Bei der Ausgabe NL auf CR abbilden. (XSI; nicht in allen POSIX- oder Single-Unix-Spezifikationssystemen verfügbar.)
  • ONOCR: CR in der ersten Spalte überspringen (nicht ausgeben).
  • ONLRET: CR überspringen (nicht ausgeben).

Sie könnten beispielsweise vermeiden, sich auf das ttyModul zu verlassen. Die "makeraw" -Operation löscht nur eine Reihe von Flags (und setzt die 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

Aus Kompatibilitätsgründen sollten Sie jedoch zunächst prüfen, ob alle diese Konstanten im Modul termios vorhanden sind (wenn Sie auf Nicht-POSIX-Systemen ausgeführt werden). Sie können auch mit new_settings[6][termios.VMIN]und new_settings[6][termios.VTIME]festlegen, ob und wie lange ein Lesevorgang blockiert werden soll, wenn keine Daten anstehen (in ganzzahligen Anzahl von Entscheidungssekunden). ( Wird normalerweise VMINauf 0 und VTIMEauf 0 gesetzt, wenn Lesevorgänge sofort zurückgegeben werden sollen, oder auf eine positive Zahl (Zehntelsekunden), wie lange der Lesevorgang höchstens warten soll.)

Wie Sie sehen können, deaktivieren die obigen (und "Makeraw" im Allgemeinen) alle Übersetzungen bei der Eingabe, was das Verhalten erklärt, das Katze sieht:

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

Um ein normales Verhalten zu erzielen, lassen Sie die Zeilen weg, die diese drei Zeilen löschen, und die Eingabeübersetzung bleibt auch bei "roh" unverändert.

Die new_settings[1] = new_settings[1] & ~termios.OPOSTZeile deaktiviert die gesamte Ausgabeverarbeitung, unabhängig davon, was die anderen Ausgabe-Flags sagen. Sie können es einfach weglassen, um die Ausgabeverarbeitung intakt zu halten. Dadurch bleibt die Ausgabe auch im Raw-Modus "normal". (Es hat keinen Einfluss darauf, ob die Eingabe automatisch wiedergegeben wird oder nicht. ECHODies wird durch das Flag in gesteuert new_settings[3].)

Wenn neue Attribute festgelegt werden, ist der Aufruf erfolgreich, wenn eine der neuen Einstellungen festgelegt wurde. Wenn die Einstellungen vertraulich sind - beispielsweise wenn Sie in der Befehlszeile nach einem Kennwort fragen -, sollten Sie die neuen Einstellungen abrufen und sicherstellen, dass die wichtigen Flags richtig gesetzt / nicht gesetzt sind.

Wenn Sie Ihre aktuellen Terminaleinstellungen anzeigen möchten, führen Sie aus

stty -a

Die Eingabe-Flags befinden sich normalerweise in der vierten Zeile und die Ausgabe-Flags in der fünften Zeile, wobei -dem Flag-Namen ein vorangestellt wird, wenn das Flag nicht gesetzt ist. Beispielsweise könnte die Ausgabe sein

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

Bei Pseudoterminals und USB-TTY-Geräten spielt die Baudrate keine Rolle.

Wenn Sie Bash-Skripte schreiben, die z. B. Passwörter lesen möchten, berücksichtigen Sie die folgende Redewendung:

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

Die EXITTrap wird ausgeführt, wenn die Shell beendet wird. Der stty -gliest die aktuellen Einstellungen des Terminals zu Beginn des Skripts, sodass die aktuellen Einstellungen beim Beenden des Skripts automatisch wiederhergestellt werden. Sie können das Skript sogar mit Ctrl+ unterbrechen C, und es wird das Richtige tun. (In einigen Eckfällen mit Signalen habe ich festgestellt, dass das Terminal manchmal mit den rohen / nicht-kanonischen Einstellungen feststeckt (es muss reset+ Enterblind am Terminal eingegeben werden), aber stty sanevor dem Wiederherstellen der tatsächlichen ursprünglichen Einstellungen wurde dies jedes Mal behoben Ich. Deshalb ist es da, eine Art zusätzliche Sicherheit.)

Sie können Eingabezeilen (die nicht an das Terminal gesendet wurden) mit der integrierten readBash-Funktion lesen oder die Eingabe sogar zeichenweise mit der Funktion lesen

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

Wenn Sie nicht IFSauf ASCII NUL setzen, readwerden die Trennzeichen von der integrierten Funktion verbraucht, sodass csie leer sind. Falle für junge Spieler.

Nominelles Tier
quelle
1
Oh, um Gottes willen, nichts ist jemals einfach :(
Katze
Ich akzeptiere diese Antwort, weil sie mir als Python-
cat
2
@cat: Auch wenn dies für Sie am hilfreichsten ist, würde ich dennoch sagen, dass die Antwort von Thomas Dickey korrekter ist . Ich möchte lieber, dass du das akzeptierst.
Nominale Tier
4
Während Ihre Bereitschaft, auf Ihre +15 Mitarbeiter zu verzichten, Ihnen zu Gute kommt, ist @cat ganz richtig. Ob eine Antwort akzeptiert wird oder nicht, ist kein Hinweis darauf, dass es sich um die "richtigste" der geposteten Antworten handelt. Es bedeutet nur, dass dies derjenige ist, den das OP aus welchen persönlichen Gründen auch immer bevorzugte. Das "Richtigste" ist in der Regel das am besten bewertete. Das Akzeptieren einer Antwort hängt von Ihrer persönlichen Präferenz ab. Wenn das OP Ihre Präferenz hat, gibt es keinen Grund, diese nicht zu akzeptieren.
Terdon
1
@terdon: Okay, dann stehe ich korrigiert da.
Nominal Animal
30

Im Wesentlichen "weil es seit manuellen Schreibmaschinen so gemacht wird". Ja wirklich.

Eine manuelle Schreibmaschine hatte einen Wagen, in den das Papier eingezogen wurde, und bewegte sich vorwärts, während Sie tippten (Laden einer Feder), und hatte einen Hebel oder Schlüssel, der den Wagen freigab, wodurch die Feder den Wagen zum linken Rand zurückbringen ließ.

Mit der Einführung der elektronischen Dateneingabe (Fernschreiben usw.) wurde dies fortgeführt. So würde der EnterSchlüssel auf vielen Terminals beschriftet sein Return.

Zeilenvorschübe erfolgten (im manuellen Verfahren), nachdem der Wagen wieder am linken Rand positioniert wurde. Wiederum ahmten die elektronischen Geräte die manuellen Geräte nach und führten eine separate line-feedOperation durch.

Beide Operationen sind codiert (damit der Teletyp mehr als ein eigenständiges Gerät ist, das einen Papiertyp erstellt). Wir haben also CR(Wagenrücklauf) und LF(Zeilenvorschub). Dieses Bild aus ASR 33 Teletype Information zeigt die Tastatur mit Returnauf der rechten Seite und Line-Feeddirekt auf der linken Seite. Auf der rechten Seite zu sein , war der Hauptschlüssel:

Bildbeschreibung hier eingeben

Unix kam später vorbei. Die Entwickler wollten Dinge verkürzen (schauen Sie sich alle Abkürzungen an, auch creatfür "create"). Angesichts eines möglicherweise zweiteiligen Prozesses entschieden sie, dass Zeilenvorschübe nur Sinn machten, wenn ihnen Wagenrückläufe vorausgingen. Sie haben also die expliziten Zeilenumbrüche aus Dateien gelöschtReturn Zeilenumbrüche und den Schlüssel des Terminals übersetzt , um den entsprechenden Zeilenvorschub zu senden. Um Verwirrung zu vermeiden, wurde der Zeilenvorschub als "Zeilenvorschub" bezeichnet.

Wenn Sie Text auf das Terminal schreiben, übersetzt Unix in die andere Richtung: Ein Zeilenvorschub wird zum Wagenrücklauf / Zeilenvorschub.

(Das heißt, "normal": sogenannter "gekochter Modus", im Gegensatz zum "rohen" Modus, in dem keine Übersetzung erfolgt).

Zusammenfassung:

  • Wagenrücklauf / Zeilenvorschub ist die Folge 13 10
  • das Gerät sendet 13 (seit "für immer" in Ihren Begriffen)
  • Unix-ähnliche Systeme ändern dies in 13 10
  • In anderen Systemen werden nicht unbedingt nur 10 gespeichert (Windows akzeptiert weitgehend nur 10 oder 13 10, je nachdem, wie wichtig die Kompatibilität ist).
Thomas Dickey
quelle
1
Ich suchte ein schönes Bild, um die Hebel für eine manuelle Schreibmaschine zu zeigen, fand aber nur Bilder mit niedriger Auflösung.
Thomas Dickey
3
Wenn Sie einen davon eingeben müssten, würden Sie auch alles abkürzen!
Michael Hampton
3
Was den Geschichtsteil betrifft: Die manuellen Schreibmaschinen, die ich in meinem Gebrauch benutzte, hatten ähnlich wie diese nur einen Hebel. Wenn Sie daran zogen, kurbelte es zuerst die Walze an (Zeilenvorschub) und dann zog es einfach den Wagen entlang. Und es war dieser Zug, der die Feder belastete. Mit jedem eingegebenen Buchstaben oder gedrückten Tabulator wird die Feder etwas gelöst und der Wagen in die Position "unbeladen" zurückbewegt, die sich am Ende der Zeile und nicht am Anfang befand.
RealSkeptic
2
Bei der Eingabe wird CR (von der Tty-Line-Disziplin) in LF übersetzt, nicht in CR LF. Es geht um die Ausgabe (einschließlich des Echos der Eingabe), LF in die übersetzt wird CR LF. Wenn Sie foo<Return>im gekochten Modus tippen , liest die Anwendung foo\nund foo\r\nwird von der Zeilendisziplin zurückgesendet, damit das Echo an das Terminal gesendet wird.
Stéphane Chazelas