Почему клавиша Enter не отправляет EOL?

19

Unix / Linux EOL - LF, перевод строки, ASCII 10, escape-последовательность \n.

Вот фрагмент кода Python, чтобы получить ровно одно нажатие клавиши:

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

Когда я нажимаю Enterна клавиатуре в ответ на этот фрагмент, он \rвозвращает возврат каретки, ASCII 13.

В ОС Windows , Enterпосылает CR LF == 13 10. * nix - это не Windows; почему Enterдают 13, а не 10?

Кот
источник
Попробуйте прочитать два байта.
Майкл Хэмптон
@MichaelHampton Нет, этот дескриптор файла не ждет ничего после того, как прочитан один байт
кот

Ответы:

11

В то время как ответ Томаса Дики является вполне правильным, Стефан Шазелас правильно упомянул в комментарии к ответу Дики, что преобразование не является каменным; это часть дисциплины линии.

На самом деле перевод полностью программируемый.

Страница man man 3 termios содержит в основном всю необходимую информацию. (Ссылка относится к проекту man-страниц Linux , в котором упоминаются, какие функции доступны только для Linux, а какие являются общими для POSIX или других систем; всегда проверяйте раздел Соответствие на каждой странице.)

У iflagатрибутов терминала ( old_settings[0]в коде, показанном в вопросе на Python ) есть три соответствующих флага во всех системах POSIXy:

  • INLCR: Если установлено, перевести NL в CR на входе
  • ICRNL: Если установлено (и IGNCRне установлено), перевести CR в NL на входе
  • IGNCR: Игнорировать CR при вводе

Точно так же есть соответствующие настройки вывода ( old_settings[1]):

  • OPOST: Включить обработку вывода.
  • OCRNL: Сопоставить CR с NL на выходе.
  • ONLCR: Отображение NL в CR на выходе. (XSI; доступно не во всех системах POSIX или Single-Unix-Specification.)
  • ONOCR: Пропустить (не выводить) CR в первом столбце.
  • ONLRET: Пропустить (не выводить) CR.

Например, вы можете не полагаться на ttyмодуль. Операция "makeraw" просто очищает набор флагов (и устанавливает CS8флаг):

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

хотя для совместимости вы можете сначала проверить, существуют ли все эти константы в модуле termios (если вы работаете в системах, отличных от POSIX). Вы также можете использовать new_settings[6][termios.VMIN]и, new_settings[6][termios.VTIME]чтобы указать, будет ли чтение блокироваться, если нет ожидающих данных, и как долго (в целых числах в децисекундах). (Обычно VMINустанавливается в 0 и VTIMEв 0, если чтения должны немедленно вернуться, или к положительному числу (десятые секунды), как долго чтение должно ждать максимально.)

Как вы можете видеть, вышеприведенное (и вообще «makeraw») отключает все переводы при вводе, что объясняет поведение, которое видит 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

Чтобы получить нормальное поведение, просто опустите строки, очищающие эти три строки, и входной перевод останется неизменным, даже если он «сырой».

new_settings[1] = new_settings[1] & ~termios.OPOSTСтрока отключает все обработку вывода, независимо от того, что говорят другие выходные флаги. Вы можете просто опустить его, чтобы сохранить обработку вывода без изменений. Это сохраняет вывод "нормальным" даже в необработанном режиме. (Это не влияет на то, будет ли ввод автоматически отражаться или нет; это контролируется ECHOcflag in new_settings[3].)

Наконец, когда заданы новые атрибуты, вызов будет успешным, если были установлены какие-либо новые настройки. Если настройки чувствительны - например, если вы запрашиваете пароль в командной строке - вы должны получить новые настройки и убедиться, что важные флаги правильно установлены / не установлены, чтобы быть уверенными.

Если вы хотите увидеть текущие настройки терминала, запустите

stty -a

Флаги ввода обычно находятся в четвертой строке, а флаги вывода - в пятой строке, с -предшествующим именем флага, если флаг не установлен. Например, вывод может быть

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

На псевдотерминалах и устройствах USB TTY скорость передачи данных не имеет значения.

Если вы пишете скрипты Bash, которые хотят читать, например, пароли, рассмотрите следующую идиому:

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

EXITЛовушка выполняется всякий раз , когда оболочка завершает свою работу. stty -gСчитывает текущие параметры терминала в начале сценария, так что текущие настройки будут восстановлены при выходе из сценария, автоматически. Вы можете даже прервать сценарий с помощью Ctrl+ C, и он будет делать правильные вещи. (В некоторых угловых случаях с сигналами я обнаружил, что терминал иногда застревает с необработанными / неканоническими настройками (требуется один для слепого набора reset+ Enterв терминале), но работа stty saneперед восстановлением фактических исходных настроек вылечивает, что каждый раз для я. Так вот почему это там, своего рода дополнительная безопасность.)

Вы можете читать входные строки (не подключенные к терминалу), используя readвстроенный bash, или даже читать ввод за символом, используя

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

Если вы не установите IFSASCII NUL, readвстроенный будет использовать разделители, так что cон будет пустым. Ловушка для молодых игроков.

Номинальное животное
источник
1
О, ради богов, ничего не всегда просто :(
кот
Я принимаю этот ответ, потому что он наиболее полезен для меня, как разработчика Python, хотя другой замечательный
cat
2
@cat: Хотя это может быть наиболее полезным для вас, я бы сказал, что ответ Томаса Дики более правильный . Я бы предпочел, чтобы вы приняли это вместо этого.
Номинальное животное
4
В то время как ваша готовность отказаться от своего +15 представителя делает вам заслугу, @cat совершенно прав. Принято ли ответ или нет, не означает, что он является «наиболее правильным» из опубликованных ответов. Это означает только то, что ОП предпочитает по личным причинам. «Самый правильный», как правило, является наиболее высоко голосуемым. Принятие ответа зависит только от личных предпочтений. Если ОП предпочитает ваш, то нет никаких причин не принимать его.
Terdon
1
@terdon: Хорошо, тогда я исправлюсь.
Номинальное животное
30

По сути, «потому что это было сделано так с ручных пишущих машинок». В самом деле.

Ручная пишущая машинка имела каретку, на которую подавалась бумага, и она двигалась вперед по мере того, как вы печатали (загружая пружину), и имела рычаг или ключ, который отпускал каретку, позволяя пружине возвращать каретку на левое поле.

Когда был введен электронный ввод данных (телетайп и т. Д.), Они продвинулись вперед. Таким образом, Enterключ на многих терминалах будет помечен Return.

Перевод строки произошел (в ручном режиме) после возврата каретки на левое поле. Опять же, электронные устройства имитировали ручные устройства, делая отдельную line-feedоперацию.

Обе операции закодированы (чтобы телетайп мог быть больше, чем автономное устройство, создающее тип бумаги), поэтому мы имеем CR(возврат каретки) и LF(перевод строки). Это изображение из ASR 33 Teletype Information показывает клавиатуру, Returnсправа и Line-Feedслева. Находясь справа , это был главный ключ:

введите описание изображения здесь

Unix появился позже. Его разработчики любили сокращать вещи (посмотрите на все сокращения, даже creatдля «создания»). Столкнувшись, возможно, с двумя частями процесса, они решили, что переводы строки имеют смысл только в том случае, если им предшествует возврат каретки. Поэтому они отбросили явные возвраты каретки из файлов и перевели Returnключ терминала для отправки соответствующего перевода строки. Просто, чтобы избежать путаницы, они называли перевод строки «новой строкой».

При написании текста на терминале Unix переводит в другом направлении: перевод строки становится переводом каретки / переводом строки.

(То есть «обычно»: так называемый «режим приготовления», в отличие от «необработанного» режима, когда перевод не выполняется).

Резюме:

  • возврат каретки / перевод строки это последовательность 13 10
  • устройство посылает 13 (так как «навсегда» в ваших терминах)
  • Unix-подобные системы меняют это на 13 10
  • Другие системы не обязательно хранят только 10 (Windows в основном принимает только 10 или 13 10, в зависимости от того, насколько важна совместимость).
Томас Дики
источник
1
Я искал красивую картинку, чтобы показать рычаги для ручной пишущей машинки, но нашел только изображения с низким разрешением.
Томас Дики
3
Если бы вам пришлось печатать на одном из них, вы бы тоже все сокращали!
Майкл Хэмптон
3
Что касается исторической части: ручные пишущие машинки, которые я использовал в своей работе, похожи на эту, у них был только один рычаг. Когда вы тянете его, он сначала проворачивает ролик (перевод строки), а затем он просто тянет каретку вперед. И именно этот рывок нагружал пружину. Каждая набранная буква или нажатие на клавишу табло несколько отпускали пружину, возвращая каретку в положение «без нагрузки», которое находилось в конце строки, а не в ее начале.
RealSkeptic
2
При вводе CR переводится (по дисциплине tty line) в LF, а не CR LF. Это на выходе (включая эхо ввода), который LF переводится в CR LF. Когда вы набираете foo<Return>в готовом режиме, приложение читает foo\nи foo\r\nотсылается по дисциплине линии для эха на терминал.
Стефан Шазелас