Чому клавіша Enter не надсилає EOL?


19

Unix / Linux EOL - це LF, передача ліній, ASCII 10, послідовність виходу \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 Nope, дескриптора файлу нічого не чекає після того, як буде прочитаний один байт
кіт

Відповіді:


11

Поки відповідь Томаса Дікі є цілком правильною, Стефан Шазелас правильно згадував у коментарі до відповіді Дікі, що перетворення не встановлено в камінь; це частина лінійної дисципліни.

Насправді переклад є повністю програмованим.

Сторінка man 3 terios містить в основному всю відповідну інформацію. (Посилання стосується проекту man-pages Linux , в якому зазначається, які функції є лише для Linux, а які є загальними для POSIX або інших систем; завжди перевірте розділ Conforming to Conforming на кожній сторінці.)

В 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-специфікація.)
  • ONOCR: Пропустити (не виводити) CR у першому стовпці.
  • ONLRET: Пропустити (не виводити) CR.

Наприклад, ви можете уникнути покладання на ttyмодуль. Операція "makeraw" просто очищає набір прапорів (і встановлює CS8thelaglag):

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

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

Як бачимо, вищевказане (та "makeraw" взагалі) вимикає весь переклад на вході, що пояснює поведінку кішки:

    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 в 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

Якщо ви не встановите IFSна ASCII NUL, readвбудований буде споживати роздільники, так що cвін буде порожнім. Пастка для молодих гравців.


1
О, ради богів, ніколи не буває просто :(
кіт

Я приймаю цю відповідь, тому що вона мені найбільше допомагає як півоня Пітона, хоча інший чудовий
кіт

2
@cat: Хоча це може бути для вас найбільш корисним, я все-таки скажу, що відповідь Томаса Дікі є правильнішою . Я вважаю за краще, щоб ви прийняли це замість цього.
Номінальна тварина

4
Незважаючи на те, що ваша готовність відмовитись від своїх +15 представників, ви вважаєте, @cat цілком вірна. Прийнята відповідь чи ні, це не свідчить про те, що це "найправильніше" з опублікованих відповідей. Це означає лише те, що ОП віддало перевагу з будь-яких особистих причин. "Найправильніший", як правило, є найбільш прихильним. Прийняття відповіді зводиться до особистих переваг, якщо ОП віддає перевагу вашим, немає причин не приймати її.
тердон

1
@terdon: Гаразд, я виправлений, значить.
Номінальна тварина

30

По суті, "тому що це робиться так, починаючи з ручних машинок". Дійсно.

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

По мірі введення електронних даних (телетайп тощо) вони просували це вперед. Таким чином, Enterключ на багатьох терміналах був би позначений Return.

Лінійні канали траплялися (в ручному процесі) після повернення вагона до лівого краю. Знову ж таки, електронні пристрої імітували ручні пристрої, роблячи окрему line-feedоперацію.

Обидві операції кодуються (щоб телетайп був більше ніж окремий пристрій, що створює тип паперу), тому у нас є CR(повернення-перевезення) та LF(подача рядків). На цьому зображенні з інформації про телерадіомобіль ASR 33 зображено клавіатуру з 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) в LF, а не CR LF. Це LF переводиться на вихід (включаючи відлуння введення), на який перекладається CR LF. Коли ви набираєте foo<Return>приготовлений режим, програма читає foo\nі foo\r\nвідправляється назад за допомогою рядкової дисципліни для відлуння до терміналу.
Стефан Шазелас

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.