Dlaczego klawisz Enter nie wysyła EOL?

19

EOL Unix / Linux to LF, linefeed, ASCII 10, sekwencja zmiany znaczenia \n.

Oto fragment kodu w języku Python, aby uzyskać dokładnie jedno naciśnięcie klawisza:

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

Kiedy naciskam Enterklawiaturę w odpowiedzi na ten fragment \rkodu , daje znak powrotu karetki, ASCII 13.

Na systemie Windows , Enterwysyła CR LF == 13 10. * nix nie jest systemem Windows; dlaczego Enterdaje 13 zamiast 10?

kot
źródło
Spróbuj odczytać dwa bajty.
Michael Hampton
@MichaelHampton Nie, nic nie czeka na ten deskryptor pliku po odczytaniu jednego bajtu
kot

Odpowiedzi:

11

Chociaż odpowiedź Thomasa Dickeya jest całkiem poprawna, Stéphane Chazelas słusznie wspomniał w komentarzu do odpowiedzi Dickeya, że ​​nawrócenie nie jest ugruntowane; jest częścią dyscypliny liniowej.

W rzeczywistości tłumaczenie jest całkowicie programowalne.

Man 3 termios strona podręcznika zawiera w zasadzie wszystkie istotne informacje. (Odsyłacz prowadzi do projektu stron podręcznika systemu Linux , w którym wspomniano, które funkcje są dostępne tylko w systemie Linux i które są wspólne dla POSIX lub innych systemów; zawsze sprawdź tam sekcję Zgodność z na każdej stronie).

Te iflagatrybuty terminali ( old_settings[0]w kodzie przedstawionym w pytaniu w Pythonie ) ma trzy odpowiednie flagi na wszystkich systemach POSIXy:

  • INLCR: Jeśli jest ustawiony, przetłumacz NL na CR na wejściu
  • ICRNL: Jeśli jest ustawiony (i IGNCRnie jest ustawiony), przetłumacz CR na NL na wejściu
  • IGNCR: Zignoruj ​​CR na wejściu

Podobnie istnieją również powiązane ustawienia wyjściowe ( old_settings[1]):

  • OPOST: Włącz przetwarzanie wyjściowe.
  • OCRNL: Mapuj CR na NL na wyjściu.
  • ONLCR: Mapuj NL na CR na wyjściu. (XSI; nie jest dostępny we wszystkich systemach POSIX lub Single-Unix-Specification.)
  • ONOCR: Pomiń (nie wysyłaj) CR w pierwszej kolumnie.
  • ONLRET: Pomiń (nie wysyłaj) CR.

Na przykład możesz uniknąć polegania na ttymodule. Operacja „makeraw” po prostu usuwa zestaw flag (i ustawia 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

chociaż ze względu na kompatybilność, możesz najpierw sprawdzić, czy wszystkie te stałe istnieją w module termios (jeśli działasz na systemach innych niż POSIX). Możesz także użyć new_settings[6][termios.VMIN]i, new_settings[6][termios.VTIME]aby ustawić, czy odczyt będzie blokowany, jeśli nie ma żadnych oczekujących danych, i jak długo (w liczbach całkowitych w decisekundach). (Zazwyczaj VMINjest ustawiony na 0 i VTIMEna 0, jeśli odczyty powinny natychmiast powrócić, lub do liczby dodatniej (dziesiąta sekunda), jak długo odczyt powinien czekać najwyżej.)

Jak widać powyższe (i ogólnie „ekspresaw”) wyłącza wszystkie tłumaczenia na wejściu, co wyjaśnia zachowanie, które widzi 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

Aby uzyskać normalne zachowanie, po prostu pomiń wiersze usuwające te trzy wiersze, a tłumaczenie wejściowe pozostanie niezmienione, nawet gdy „surowe”.

new_settings[1] = new_settings[1] & ~termios.OPOSTLinia wyłącza wszystkie wyjścia przetwarzania, niezależnie co inne flagi wyjściowe powiedzieć. Możesz to pominąć, aby zachować nienaruszone przetwarzanie danych wyjściowych. Dzięki temu wyjście jest „normalne”, nawet w trybie surowym. (Nie wpływa to na to, czy sygnał wejściowy jest automatycznie powtarzany, czy nie; jest to kontrolowane przez ECHOcflag in new_settings[3].)

Na koniec, gdy zostaną ustawione nowe atrybuty, połączenie zakończy się powodzeniem, jeśli zostaną ustawione jakiekolwiek nowe ustawienia. Jeśli ustawienia są wrażliwe - na przykład, jeśli pytasz o hasło w wierszu poleceń - powinieneś uzyskać nowe ustawienia i upewnić się, że ważne flagi są poprawnie ustawione / wyłączone.

Jeśli chcesz zobaczyć swoje aktualne ustawienia terminala, uruchom

stty -a

Flagi wejściowe są zwykle w czwartym wierszu, a flagi wyjściowe w piątym wierszu, z -poprzedzającą nazwą flagi, jeśli flaga jest rozbrojona. Na przykład dane wyjściowe mogą być

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

Na pseudoterminalach i urządzeniach USB TTY szybkość transmisji jest nieistotna.

Jeśli piszesz skrypty Bash, które chcą czytać np. Hasła, rozważ następujący idiom:

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

EXITPułapka jest wykonywany, gdy powłoka wychodzi. stty -gOdczytuje aktualne ustawienia terminal na początku skryptu, więc obecne ustawienia są przywracane po wyjściu skrypcie automatycznie. Możesz nawet przerwać skrypt za pomocą Ctrl+ C, a zrobi to dobrze. (W niektórych narożnych przypadkach z sygnałami stwierdziłem, że terminal czasami blokuje się w ustawieniach surowych / niekanonicznych (wymaga wpisania reset+ na Enterślepo w terminalu), ale uruchomienie stty saneprzed przywróceniem faktycznych ustawień wyleczyło to za każdym razem ja. To dlatego tam jest; coś w rodzaju dodatkowego bezpieczeństwa.)

Możesz czytać wiersze wejściowe (niezwiązane z terminalem) za pomocą readwbudowanego bash, a nawet czytać dane wejściowe znak po znaku za pomocą

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

Jeśli nie ustawisz IFSASCII NUL, readwbudowane zużyją separatory, więc cbędą puste. Pułapka dla młodych graczy.

Nominalne zwierzę
źródło
1
Och, na miłość boską, nic nie jest nigdy takie proste :(
kot
Przyjmuję tę odpowiedź, ponieważ jest ona dla mnie bardzo pomocna jako twórca Pythona, mimo że druga jest świetna
kot
2
@cat: Choć może ci to być najbardziej pomocne, nadal powiedziałbym, że odpowiedź Thomasa Dickeya jest bardziej poprawna . Wolę raczej to zaakceptować.
Nominal Animal
4
Chociaż chęć zrezygnowania z powtórzenia z +15 Ci się podoba, @cat ma rację. To, czy odpowiedź zostanie zaakceptowana, czy nie, nie oznacza, że ​​jest to „najbardziej poprawna” z opublikowanych odpowiedzi. Oznacza to tylko, że ten preferował PO z jakichkolwiek osobistych powodów. „Najbardziej poprawny” jest zwykle najbardziej wysoko oceniany. Akceptacja odpowiedzi zależy od osobistych preferencji, jeśli PO woli twoją, nie ma powodu, aby jej nie akceptować.
terdon
1
@terdon: Okej, więc poprawiam się.
Nominal Animal
30

Zasadniczo „ponieważ zrobiono to w ten sposób od ręcznych maszyn do pisania”. Naprawdę.

Ręczna maszyna do pisania miała karetkę, na której podawano papier, i poruszała się do przodu podczas pisania (ładowanie sprężyny), i miała dźwignię lub klucz, który zwalniałby karetkę, pozwalając sprężynie powrócić karetkę do lewego marginesu.

Gdy wprowadzono elektroniczne wprowadzanie danych (teletyp itp.), Posunięto je dalej. Tak więc Enterklucz na wielu terminalach byłby oznaczony Return.

Wprowadzanie linii nastąpiło (w procesie ręcznym) po przywróceniu karetki do lewego marginesu. Ponownie urządzenia elektroniczne imitowały urządzenia ręczne, wykonując osobną line-feedoperację.

Obie operacje są zakodowane (aby pozwolić, aby typ teletekstu był czymś więcej niż samodzielnym urządzeniem tworzącym typ papieru), więc mamy CR(powrót karetki) i LF(przesunięcie wiersza). Ten obraz z ASR 33 Teletype Information pokazuje klawiaturę, Returnpo prawej stronie i Line-Feedpo lewej stronie. Będąc po prawej stronie , był to główny klucz:

wprowadź opis zdjęcia tutaj

Unix pojawił się później. Jego twórcy lubili skracać rzeczy (spójrz na wszystkie skróty, nawet creat„twórz”). W obliczu prawdopodobnie dwuczęściowego procesu zdecydowali, że przesuwanie wiersza ma sens tylko wtedy, gdy poprzedza je powrót karetki. Usunęli więc jawne zwroty karetki z plików i przetłumaczyli Returnklucz terminala, aby wysłać odpowiedni znak wiersza. Aby uniknąć nieporozumień, określali wiersz jako „nowy wiersz”.

Podczas pisania tekstu na terminalu Unix tłumaczy w innym kierunku: znak wiersza staje się znakiem powrotu karetki / znakiem wiersza.

(To znaczy „normalnie”: tak zwany „tryb gotowany”, w przeciwieństwie do trybu „surowego”, w którym nie wykonuje się tłumaczenia).

Streszczenie:

  • powrót karetki / przesunięcie wiersza to sekwencja 13 10
  • urządzenie wysyła 13 (od „zawsze” w swoich kategoriach)
  • Systemy uniksowe zmieniają to na 13 10
  • Inne systemy niekoniecznie przechowują tylko 10 (Windows w dużej mierze akceptuje tylko 10 lub 13 10, w zależności od tego, jak ważna jest kompatybilność).
Thomas Dickey
źródło
1
Szukałem ładnego zdjęcia, które pokazuje dźwignie ręcznej maszyny do pisania, ale znalazłem tylko obrazy o niskiej rozdzielczości.
Thomas Dickey,
3
Gdybyś musiał wpisać jeden z nich, wszystko też skróciłbyś!
Michael Hampton
3
Jeśli chodzi o część historyczną: używane przeze mnie ręczne maszyny do pisania, podobne do tej, miały tylko jedną dźwignię. Kiedy go pociągnąłeś, najpierw obrócił wałek (podawanie linii), a następnie po prostu pociągnął wózek. I to właśnie ta siła naciągnęła sprężynę. Każda litera lub naciśnięcie klawisza zwalnia nieco sprężynę, przesuwając karetkę z powrotem do pozycji „rozładowanej”, która znajdowała się na końcu linii, a nie na początku.
RealSkeptic
2
Na wejściu CR jest tłumaczony (przez dyscyplinę linii tty) na LF, a nie CR LF. Jest na wyjściu (w tym echo wejścia), na które LF jest tłumaczone CR LF. Gdy piszesz foo<Return>w trybie gotowym, aplikacja odczytuje foo\ni foo\r\njest wysyłana z powrotem przez dyscyplinę liniową w celu wykonania echa do terminala.
Stéphane Chazelas