Como sinalizar o fim da entrada para “ler N”?

5

Eu tenho tentado descobrir por que recebo um caractere de fim de transmissão literal (EOT, ASCII código 4) na minha variável se eu ler Ctrl + D com read -N 1 em bash e ksh93 .

Estou ciente da distinção entre o caractere de fim de transmissão e a condição de fim de arquivo, e eu sei o que Ctrl + D faz ao usar read sem -N (envia EOT e, se a entrada estiver vazia, o read() subjacente retorna zero, sinalizando EOF).

Mas não sei por que tentar ler um número específico de caracteres altera esse comportamento tão radicalmente. Eu teria esperado uma condição de EOF e que o loop seguinte sairia:

while read -N 1 ch; do
  printf '%s' "$ch" | od
done

Saída ao pressionar Ctrl + D :

0000000  000004
0000001

O manual bash diz sobre read -N ( ksh93 tem uma redação semelhante):

-N nchars; read returns after reading exactly nchars characters rather than waiting for a complete line of input, unless EOF is encountered or read times out.

... mas não diz nada sobre mudar o TTY para o modo raw / unbuffered (que é o que eu suponho que esteja acontecendo).

A opção -n para read parece funcionar da mesma maneira no que diz respeito a Ctrl + D , e o número de caracteres a ler também não parece importar.

Como posso sinalizar um fim de entrada para read -N e sair do loop (além de testar o valor lido), e por que isso é diferente de um "bare" read ?

    
por Kusalananda 24.01.2017 / 01:55

1 resposta

5

Poderia ser mais útil se o documento apontasse que não existe um ASCII EOF, que a semântica ASCII para ^ D é EOT, que é o que o driver do terminal fornece no modo canônico: ele encerra a transmissão atual, o código%. Programas interpretam um comprimento 0 lido como EOF, porque é como o EOF se parece em arquivos que possuem isso, mas o driver do terminal se recusando a entregar o código de caractere 4 e em vez de engoli-lo e encerrar a leitura não é sempre o que você quer.

Isso é o que está acontecendo aqui: a semântica de caracteres de controle faz parte do modo canônico, o modo em que o driver de terminal armazena em buffer até ver um caractere ao qual a convenção atribui um significado especial. Isto é verdade para EOT, BS, CR e para uma série de outros (veja read e stty -a para todos os detalhes).

man termios é uma ordem explícita para entregar apenas os próximos N caracteres. Para fazer isso, o shell tem que parar de pedir ao driver do terminal por semântica canônica.

A propósito, o EOF não é realmente uma condição que um terminal pode definir ou inserir.

Se você continuar lendo o eof em qualquer outra coisa, você continuará recebendo o indicador EOF, mas o único EOF que o driver do terminal pode fornecer é falso - pense nisso - se o driver do terminal realmente forneceu um EOF real, então o shell não pôde continuar lendo depois. É tudo o mesmo terminal. Aqui:

#include <unistd.h>
#include <stdio.h>
char s[32];
int main(int c, char**v)
{
    do {
        c=read(0,s,sizeof s);
        printf("%d,%.*s\n",c,c,s);
    } while (c>=0);
}

tente que no terminal, você verá que o driver de terminal no modo canônico apenas interpreta EOT para concluir qualquer leitura pendente, e ele armazena internamente até ver algum terminador de entrada canônico, independentemente do tamanho do buffer de leitura (digite uma linha mais de 32 bytes).

O texto que está confundindo você

unless EOF is encountered

está se referindo a um EOF real.

    
por 24.01.2017 / 10:59