Embora a resposta de Thomas Dickey esteja correta, Stéphane Chazelas mencionou corretamente em um comentário à resposta de Dickey que a conversão não é gravada na pedra; faz parte da disciplina de linha.
Na verdade, a tradução é totalmente programável.
A página man 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 somente Linux, e quais são comuns para POSIX ou outros sistemas, sempre verifique a seção Conforme-se em cada página lá.)
Os atributos do terminal iflag
( old_settings[0]
no código mostrado na pergunta em Python ) tem três bandeiras relevantes em todos os sistemas POSIXy:
-
INLCR
: Se definido, traduza NL para CR na entrada -
ICRNL
: Se definido (eIGNCR
não está definido), traduza CR para NL na entrada -
IGNCR
: Ignorar CR na entrada
Da mesma forma, existem configurações de saída relacionadas ( old_settings[1]
) também:
-
OPOST
: ativar o processamento de saída. -
OCRNL
: mapeia 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
: Ignora (não sai) CR na primeira coluna. -
ONLRET
: pula (não sai) CR.
Por exemplo, você pode evitar confiar no módulo tty
. 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 por questões de compatibilidade, você pode querer verificar se todas as constantes existem no módulo termios primeiro (se você rodar em sistemas não POSIX). Você também pode usar new_settings[6][termios.VMIN]
e new_settings[6][termios.VTIME]
para definir se uma leitura será bloqueada se não houver dados pendentes e por quanto tempo (em número inteiro de deciseconds). (Geralmente VMIN
é definido como 0 e VTIME
a 0 se as leituras devem retornar imediatamente ou a 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) desativa toda a tradução na entrada, o que explica o comportamento do gato:
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 apagam essas três linhas e a conversão de entrada não é alterada, mesmo quando "raw".
A linha new_settings[1] = new_settings[1] & ~termios.OPOST
desativa todo o processamento de saída, independentemente do que os outros sinalizadores de saída disserem. Você pode simplesmente omiti-lo para manter o processamento de saída intacto. Isso mantém a saída "normal" mesmo no modo raw. (Não afeta se a entrada é ecoada automaticamente ou não; isso é controlado pelo ECHO
cflag em new_settings[3]
.)
Finalmente, quando novos atributos são definidos, a chamada será bem sucedida se qualquer das novas configurações forem definidas. 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 corretamente definidos / desfeitos, 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 -
precedendo o nome do sinalizador se o sinalizador não estiver definido. Por exemplo, a saída poderia 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
Em pseudoterminais e dispositivos USB TTY, a taxa de transmissão é irrelevante.
Se você escrever 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
O EXIT
trap é executado sempre que o shell é encerrado. O stty -g
lê as configurações atuais do terminal no início do script, então as configurações atuais são restauradas quando o script sai automaticamente. Você pode até mesmo interromper o script com Ctrl + C , e ele fará a coisa certa. (Em alguns casos de canto com sinais, descobri que o terminal às vezes fica preso com as configurações raw / noncanonical (exigindo que alguém digite reset
+ Enter cegamente no terminal), mas executando stty sane
antes de restaurar as configurações originais reais tem curado isso toda vez para mim. Então é por isso que está lá, uma espécie de segurança adicional.)
Você pode ler linhas de entrada (não instaladas no terminal) usando read
bash embutido, ou mesmo ler a entrada caractere por caractere usando
IFS=$'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
'
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, read
integrado consumirá os separadores, de modo que c
fique vazio. Armadilha para jovens jogadores.