В то время как ответ Томаса Дики является вполне правильным, Стефан Шазелас правильно упомянул в комментарии к ответу Дики, что преобразование не является каменным; это часть дисциплины линии.
На самом деле перевод полностью программируемый.
Страница 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
Строка отключает все обработку вывода, независимо от того, что говорят другие выходные флаги. Вы можете просто опустить его, чтобы сохранить обработку вывода без изменений. Это сохраняет вывод "нормальным" даже в необработанном режиме. (Это не влияет на то, будет ли ввод автоматически отражаться или нет; это контролируется ECHO
cflag 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
Если вы не установите IFS
ASCII NUL, read
встроенный будет использовать разделители, так что c
он будет пустым. Ловушка для молодых игроков.