Comportamento do backspace no terminal

3

Trata-se do comportamento do caractere backspace ( \b ). Eu tenho o seguinte programa em C:

int main() {
    printf("Hello\b\b");
    sleep(5);
    printf("h\n");
    return 0;
}

A saída no meu terminal é

Helho

com o cursor avançando para a primeira posição da linha seguinte.

Primeiro, a coisa toda é impressa somente após os 5 segundos de inatividade, então deduzi a saída do kernel para o terminal como buffer de linha. Então agora, minhas perguntas são:

  1. Como o \b\b volta dois espaços, para a posição do (segundo) l , então é semelhante a como l foi substituído por h , o o deveria ter sido substituído por \n . Por que não foi?
  2. Se eu remover a linha printf("h\n"); , ela imprime Hello e retorna dois caracteres, sem apagar. Isso eu recebi de outras respostas é por causa de um backspace não-destrutivo. Por que esse comportamento é diferente para entrada e saída? Isto é, se eu introduzir algo no terminal (mesmo o mesmo programa) e pressionar Backspace, apaga o último caractere, mas não a saída. Por quê?

Eu estou em um sistema Ubuntu no terminal xterm usando o bash, se isso ajudar.

    
por forumulator 01.01.2018 / 18:43

3 respostas

3

First, the entire thing prints only after the 5 second sleep so from that I deduced the output from the kernel to the terminal is line buffered.

Não, a saída do seu programa para o kernel está com buffer de linha. Esse é o comportamento padrão para stdio quando stdout é um terminal. Adicione a chamada setbuf(stdout, NULL) para desativar o buffer de saída para stdout . Veja setbuf(3) .

  1. Since the \b\b goes back two spaces, to the position of l then similar to how l was replaced by h, the o should have been replaced by \n. Why wasn't it?

Como o caractere de nova linha apenas move o cursor (e rola a tela), ele não é impresso como um caractere visível que ocuparia o lugar de um glifo no terminal. Se assumirmos que tomaria o lugar de um glifo, como seria?

  1. if I input something into the very same program, and press Backspace, it erases the last character, but not for the output. Why?

Bem, o que acontece quando você digita depende do modo em que o terminal está. Aproximadamente, ele pode estar no modo "cozido" usual, no qual o próprio terminal fornece edição de linha elementar (manipula backspaces); ou em um modo "bruto", onde todos os pressionamentos de tecla vão para o aplicativo, e cabe ao aplicativo decidir o que fazer com eles e o que enviar em resposta. O modo cozinhado geralmente acompanha o "eco local", no qual o terminal (local para o usuário) imprime os caracteres à medida que são digitados. No modo raw, o aplicativo geralmente cuida de ecoar os caracteres digitados, para ter controle total sobre o que é visível.

Ver, por exemplo, esta questão para discussão sobre os modos de terminal: Qual é a diferença entre um driver de dispositivo "cru" e um "cozido"?

Se você executar, e. cat , o terminal estará no modo cozido (o padrão) e lidará com a edição da linha. Atingir, por exemplo, x Backspace Ctrl-D resultará em cat apenas lendo a entrada vazia, sinalizando o final da entrada. Você pode verificar isso com strace . Mas se você executar um shell Bash interativo, ele manipulará o backspace por si só e exibirá o que considerar apropriado para fazer o que o usuário espera, ou seja, limpar um caractere.

Aqui está parte da saída para strace -etrace=read,write -obash.trace bash , depois de entrar na seqüência mencionada x Backspace Ctrl-D :

read(0, "x", 1)                         = 1
write(2, "x", 1)                        = 1
read(0, "7", 1)                      = 1
write(2, "[K", 4)                 = 4
read(0, "", 1)                        = 1

Primeiro, Bash read se write s x , enviando para o terminal. Em seguida, ele lê o backspace (código de caractere 0177 em octal ou 127 em decimal) e exibe o caractere de backspace (octal 010, decimal 8 (*) ) que move o cursor de volta e exibe a sequência de controle para limpar o final da linha, <ESC>[K . O último é o Ctrl-D , que é usado pelo Bash para sair do programa.

(* na entrada, Ctrl-H teria o código de caractere decimal 8. Backspace é o mesmo ou 127 como aqui, dependendo novamente de como o terminal está configurado.)

Em comparação, o mesmo experimento com cat mostra apenas uma única leitura de zero bytes, a condição "fim do arquivo". O fim do arquivo pode significar que um pipe ou socket conectado está sendo fechado, um final real do arquivo ou Ctrl-D sendo recebido de um terminal no modo cozido:

read(0, "", 131072)                     = 0

Em particular, cat não vê o x , nem o backspace, nem o código real para Ctrl-D : eles são manipulados pelo terminal. Qual pode ser o driver de terminal virtual no kernel; um terminal físico real sobre uma conexão serial ou algo semelhante; ou um emulador de terminal como o xterm rodando na mesma máquina ou na extremidade remota de uma conexão SSH. Não importa para o software do espaço do usuário.

    
por 01.01.2018 / 19:59
3

Os códigos ( \b , \n etc) movem o cursor, não o texto.

Acho que você descobrirá que o comportamento deriva dos "bons velhos tempos" quando tivemos que manipular o cursor em uma impressora para obter efeitos tão impressionantes quanto fontes em negrito e tachado em teletipo, impressoras de bolas de golfe e afins.

\b apenas move o ponto de inserção para trás e permite sobrescrever. Em uma tela digital, isso exclui o conteúdo anterior, mas em uma impressora antiga você obteria o caractere de sobreposição.

\n move o cursor para uma nova linha, mas deixa o texto na linha antiga para trás. Isso teria sido um CRLF que retorna o cursor para o início da linha e informa a impressora para rolar uma linha.

Os códigos CR, LF e CRLF estão novamente ligados à necessidade de manipular dispositivos antigos para obter esses novos recursos efeitos.

..... e não havia como excluir caracteres de uma página impressa, esperava-se que você acertasse antes de postar para imprimir.

EDITAR

Re ponto 2. Entrada e saída fazem coisas diferentes porque você está dizendo para eles.

\b é equivalente à seta para a esquerda com INSERT desativado, não excluído.

\n é equivalente a seta para baixo (LF) e HOME (CR), não à tecla "enter".

    
por 01.01.2018 / 19:14
3

Primeiro de tudo, se você quiser aprender sobre essas coisas, você deve ler

Você pergunta,

… if I input something …, and press Backspace, it erases the last character, but not for the output. Why?

Veja um pouco de trivialidades para você: Nos primeiros dias do Unix (a década de 1970, antes mesmo de existir o Linux), backspace 1 não apagou os caracteres de entrada, e os usuários os odiaram. Você digita beast , clica em Backspace três vezes e você ainda teria umbe|ast de olhos malvados olhando para você ( | representa o cursor). Mesmo depois que você digitou st (porque você quis dizer best ), a tela ainda diria best|t e era confusa.

Muitos usuários adotaram o hábito de digitar Backspace Espaço Backspace uma vez para cada personagem que eles queriam apagar, para usar espaços para substituir / destruir os personagens ruins; isto é, manualmente apagar eles. Eventualmente (1979 ou início dos anos 80), os desenvolvedores cederam e tornaram isso automático:

When you hit backspace, the kernel tty line discipline rubs out your previous character by printing (in the simple case) Ctrl-H, a space, and then another Ctrl-H.
Source: How Unix erases things when you type a backspace while entering text

onde "Ctrl-H" é o código de controle para um backspace.

Em algumas situações, você pode desativar essa opção e obtenha o comportamento antigo digitando stty -echoe ( echoe ="echo erase" e - significa "não faça isso").

por 02.01.2018 / 07:40