Enquanto a resposta de Thomas Dickey é bastante correta, Stéphane Chazelas mencionou corretamente em um comentário à resposta de Dickey que a conversão não é imutável; faz parte da disciplina de linha.
De fato, a tradução é completamente programável.
A página do manual man 3 termios contém basicamente todas as informações pertinentes. (O link leva para o projeto de páginas de manual do Linux , que menciona quais recursos são apenas para Linux e são comuns ao POSIX ou a outros sistemas; sempre verifique a seção Conforming to em cada página.)
Os iflag
atributos do terminal ( old_settings[0]
no código mostrado na pergunta em Python ) possuem três sinalizadores relevantes em todos os sistemas POSIXy:
INLCR
: Se definido, traduza NL para CR na entrada
ICRNL
: Se definido (e IGNCR
não estiver definido), traduza CR para NL na entrada
IGNCR
: Ignorar CR na entrada
Da mesma forma, também existem configurações de saída relacionadas ( old_settings[1]
):
OPOST
: Ativar o processamento de saída.
OCRNL
: Mapeie CR para NL na saída.
ONLCR
: Mapeie NL para CR na saída. (XSI; não disponível em todos os sistemas POSIX ou Single-Unix-Specification.)
ONOCR
: Ignore (não produza) CR na primeira coluna.
ONLRET
: Ignorar (não produzir) CR.
Por exemplo, você pode evitar confiar no tty
módulo. A operação "makeraw" apenas limpa um conjunto de sinalizadores (e define o CS8
oflag):
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
embora, para fins de compatibilidade, convém verificar se todas essas constantes existem primeiro no módulo termios (se você executar em sistemas não POSIX). Você também pode usar new_settings[6][termios.VMIN]
e new_settings[6][termios.VTIME]
definir se uma leitura será bloqueada se não houver dados pendentes e por quanto tempo (em número inteiro de segundos decisivos). (Normalmente, VMIN
é definido como 0 e VTIME
0 se as leituras devem retornar imediatamente ou para um número positivo (décimo de segundos) quanto tempo a leitura deve esperar no máximo.)
Como você pode ver, o acima (e "makeraw" em geral) desabilita toda a tradução na entrada, o que explica o comportamento que o gato está vendo:
new_settings[0] = new_settings[0] & ~termios.INLCR
new_settings[0] = new_settings[0] & ~termios.ICRNL
new_settings[0] = new_settings[0] & ~termios.IGNCR
Para obter um comportamento normal, omita as linhas que limpam essas três linhas e a conversão de entrada permanece inalterada, mesmo quando "bruta".
A new_settings[1] = new_settings[1] & ~termios.OPOST
linha desativa todo o processamento de saída, independentemente do que dizem os outros sinalizadores de saída. Você pode simplesmente omiti-lo para manter intacto o processamento de saída. Isso mantém a saída "normal", mesmo no modo bruto. (Isso não afeta se a entrada é ecoada automaticamente ou não; isso é controlado pelo ECHO
cflag in new_settings[3]
.)
Por fim, quando novos atributos são definidos, a chamada será bem-sucedida se alguma das novas configurações tiver sido definida. Se as configurações forem confidenciais - por exemplo, se você estiver solicitando uma senha na linha de comando -, deverá obter as novas configurações e verificar se os sinalizadores importantes estão definidos / desabilitados corretamente, para ter certeza.
Se você quiser ver as configurações atuais do terminal, execute
stty -a
Os sinalizadores de entrada geralmente estão na quarta linha e os sinalizadores de saída na quinta linha, com um -
nome anterior ao sinalizador, se o sinalizador estiver desmarcado. Por exemplo, a saída pode ser
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
Nos pseudo-terminais e nos dispositivos USB TTY, a taxa de transmissão é irrelevante.
Se você escreve scripts Bash que desejam ler, por exemplo, senhas, considere o seguinte idioma:
#!/bin/bash
trap 'stty sane ; stty '"$(stty -g)" EXIT
stty -echo -echonl -imaxbel -isig -icanon min 1 time 0
A EXIT
armadilha é executada sempre que o shell sai. Ele stty -g
lê as configurações atuais do terminal no início do script, para que as configurações atuais sejam restauradas quando o script sair automaticamente. Você pode até interromper o script com Ctrl+ C, e ele fará a coisa certa. (Em alguns casos de canto com sinais, eu descobri que o terminal às vezes fica preso às configurações brutas / não-canônicas (exigindo que você digite reset
+ Entercegamente no terminal), mas a execução stty sane
antes de restaurar as configurações originais reais curou todas as vezes É por isso que está lá; uma espécie de segurança adicional.)
Você pode ler as linhas de entrada (não associadas ao terminal) usando o read
bash embutido ou até ler a entrada caractere a caractere usando
IFS=$'\0'
input=""
while read -N 1 c ; do
[[ "$c" == "" || "$c" == $'\n' || "$c" == $'\r' ]] && break
input="$input$c"
done
Se você não definir IFS
como ASCII NUL, o interno read
consumirá os separadores, de modo que c
eles estarão vazios. Armadilha para jovens jogadores.