Por que a tecla Enter não envia EOL?

18

Unix / Linux EOL é LF, alimentação de linha, ASCII 10, seqüência de escape \n .

Aqui está um snippet do Python para obter exatamente um pressionamento de tecla:

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

Quando pressiono Enter no meu teclado em resposta a este trecho, ele fornece \r , retorno de carro, ASCII 13.

Em Windows , Enter envia CR LF == 13 10 . * nix não é o Windows; Por que Enter dá 13 ao invés de 10?

Sabe-se que o snippet do Python está correto.

    
por cat 22.02.2016 / 02:34

2 respostas

11

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 (e IGNCR 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.

    
por 22.02.2016 / 18:48
30

Essencialmente "porque tem sido feito assim desde máquinas de escrever manuais". Realmente.

Uma máquina de escrever manual tinha uma carruagem na qual o papel era alimentado, e avançava enquanto você digitava (carregando uma mola), e tinha uma alavanca ou chave que soltava o carro, deixando a Primavera retornar o carro para a margem esquerda.

À medida que a entrada eletrônica de dados (teletipo, etc) foi introduzida, eles levaram isso adiante. Então a tecla Enter em muitos terminais seria rotulada Return .

Alimentações de linha ocorreram (no processo manual) após retornar o carro para a margem esquerda. Mais uma vez, os dispositivos eletrônicos imitavam os dispositivos manuais, fazendo uma operação separada de avanço de linha .

Ambas as operações são codificadas (para permitir que o teletipo seja mais do que um dispositivo autônomo que cria um tipo de papel), por isso temos CR (carriage-return) e LF (line-feed). Esta imagem de Informações do Teletipo ASR 33 mostra o teclado, com Return no lado direito e Line-Feed apenas para a esquerda. Estando no direito , era a chave principal:

Unixapareceumaistarde.Seusdesenvolvedoresgostavamdeencurtarascoisas(vejatodasasabreviações,atécreatpara"criar"). Diante de um processo possivelmente em duas partes, eles decidiram que os feeds de linha só faziam sentido se fossem precedidos por retornos de carro. Então eles soltaram os retornos explícitos de transporte de arquivos , e traduziram a tecla Return do terminal para enviar o feed de linha correspondente. Apenas para evitar confusão, eles se referiram a feed de linha como "nova linha".

Ao escrever texto no terminal, o Unix se traduz em outra direção: um avanço de linha se torna retorno de linha / avanço de linha.

(Isto é, "normalmente": o chamado "modo cozido", em contraste com o modo "bruto", no qual não é feita tradução).

Resumo:

  • retorno de linha / feed de linha é a sequência 13 10
  • o dispositivo envia 13 (desde "para sempre" nos seus termos)
  • Sistemas semelhantes a Unix mudam para 13 10
  • Outros sistemas não armazenam necessariamente apenas 10 (o Windows aceita apenas 10 ou 13 10, dependendo da importância da compatibilidade).
por 22.02.2016 / 02:54