O que devo considerar para escrever um script que reaja em um pressionamento de tecla?

3

Eu sei que posso ler dentro de um script algo em uma variável, assim: variável = ler Mas eu tenho que pressionar enter para enviar o valor para a variável. O que eu preciso saber para enviar o valor de um pressionamento de tecla para uma variável sem pressionar Enter ou, se não, enviar para uma variável apenas para reagir quando uma determinada tecla é pressionada?

    
por Abdul Al Hazred 12.02.2015 / 14:41

2 respostas

2

Com bash , você pode usar o parâmetro -n para a função read interna para limitar o número de caracteres lidos sem exigir uma nova linha:

#!/bin/bash

echo "Ready? [Y/n]: "
read -n 1 y_or_n
echo

case "$y_or_n" in
    [Yy]|"")
        echo "you said yes"
        ;;
    *)
        echo "you said no"
        ;;
esac

Isso funciona se bash é invocado como sh ou bash .

Veja help read ou a página bash para mais detalhes.

Observe que outros shells podem não suportar o parâmetro -n para read . dash , por exemplo, não.

    
por 28.02.2015 / 20:43
2

A coisa sobre read do shell é - se você limitar sua contagem total de bytes por read ou não - ainda não vai conseguir um pressionamento de tecla por read . Ele só receberá um número fixo de caracteres por read . Para obter um pressionamento de tecla por read , você precisa bloquear sua entrada - e a melhor maneira de fazer isso provavelmente é dd . Esta é uma função de shell que eu escrevi alguns meses atrás, quando eu estava experimentando w / dd de bloqueio no terminal i / o:

printkeys()(    IFS= n=1
    set -f -- "$(tty <&2)"
    exec    <${1##[!/]*};set \t
    _dd()(  c=conv=sync,unblock i=ibs=72 b=bs=8
            dd bs=7 ${c%,*} |   dd $i o$b c$b $c
    )       2>/dev/null
    trap "  trap '' TTOU TTIN
            stty '$(stty -g
            stty raw isig inlcr)'   "   INT EXIT
    _dd |while  ${2:+:} read -r c   ||  exit 2
            do  case $#:$c in (1:): ;; 
        (*:?*)  set ":%d$@"         \
                    \'${c%"${c#?}"} ;
                c=${c#?}            ;; 
        (*)     printf "$@"         ;
        [ 0 -eq $((n=n<4?n+1:0)) ]  &&
                set '\n\r'||set \t ;;  esac
        done
)

Você vê, no caso acima, que a entrada do terminal é filtrada em dois processos dd em um pipeline. O terminal é configurado com o modo stty para raw em trap , que também restaura o estado do terminal em INT errupt ou EXIT para o que era quando a função era chamada (o que pode ser feito com CTRL + C provavelmente, ou qualquer que seja sua chave de interrupção) . No modo raw , um terminal libera a entrada para qualquer leitor assim que ele chega - pressione a tecla com o pressionamento de tecla. Não apenas empurra um byte de cada vez; Ele empurra todo o buffer o mais rápido possível. E assim, quando você pressiona a seta para CIMA, por exemplo, e seu teclado envia uma sequência de escape como:

^[[A

... que tem três bytes e empurra tudo de uma vez.

dd é especificado para satisfazer qualquer leitura assim que for oferecida - não importa o quão alto você defina seu ibs= . Isso significa que, embora eu configure com ibs=7 , quando o terminal envia apenas três bytes, uma leitura ainda é concluída. Agora, isso é difícil de lidar com a maioria dos outros utilitários, mas dd ' conv=sync preenche a diferença com ddNUL bytes. Portanto, quando você pressiona a seta para cima no teclado, dd lê três bytes e grava 7 no próximo ddNUL - os três bytes na seqüência de escape e mais 4 conv=unblock s.

Mas, para extrair esses dados com a leitura do shell, é preciso bloqueá-los novamente, para que o próximo unblock sincronize seu buffer de entrada para 72 bytes - mas também dd s. Com a \n conversion, cbs= divide sua entrada em sync ewline delimiters para sua unblock count - que está aqui 8. Com block e dd (ou ddNUL ) conversões, dd não sincroniza em unblockNUL s, mas sim em espaços à direita. Assim, para cada 7 bytes, o primeiro cbs= grava no pipe entre eles, o segundo dd armazena 72 bytes - os primeiros são o que quer que seja o pressionamento de tecla, então obs=8 s, então 65 espaços na cauda de cada leitura .

A outra coisa que dd faz é eliminar os espaços à direita: ele consome tantos espaços quanto pode ocorrer no final de cada um de seus blocos de conversão dd . Portanto, porque while grava a saída em read bytes por gravação, grava 9 linhas por leitura em 2 gravações totais no canal de saída. A primeira escrita é a primeira linha e consiste nos 7 bytes lidos do canal de entrada e uma nova linha final - mais um byte. A próxima gravação é de 8 novas linhas - todas de uma vez e em linha - mais 8 bytes - porque inlcr consome todos os 8 espaços para cada um desses 8 blocos de conversão.

No outro lado desses dois stty s, um shell $c loop read s linha por linha. Ele pode ignorar linhas em branco - porque o terminal está convertendo todas as novas linhas para retornos de carro na saída de qualquer maneira, de acordo com a opção $c ' . Mas quando ele detecta um único byte em printf depois que a entrada do shell é $c em seu valor, você tem um pressionamento de tecla - e um inteiro também, desde que seu teclado não esteja enviando mais de 7 bytes por tecla. (embora isso só precisaria de um fator de bloqueio diferente) .

Quando o shell tem um pressionamento de tecla em ddNUL , itera sobre ele o byte para byte e o divide em um array dividido por zshNUL caracteres, então bash s os valores decimais para cada byte em dash all de uma vez só. Se você executar essa função, deverá obter uma saída assim:

a:97    b:98    c:99    d:100   e:101
f:102   ;:59    ^M:13   ^M:13   ^M:13
s:115   a:97    d:100   f:102    :32
':39    ':39    ':39    a:97    s:115
d:100   f:102   ;:59    ':39    ^[[A:27:91:65
^[[D:27:91:68   ^[[B:27:91:66   ^[[C:27:91:67   ^[[D:27:91:68   ^[[C:27:91:67
^[[A:27:91:65   ^[[D:27:91:68   ^[[C:27:91:67   ^[[B:27:91:66   ^[[D:27:91:68
^[[C:27:91:67   ^[[A:27:91:65   ^[[D:27:91:68   ^[[C:27:91:67   ^[[B:27:91:66

Como o ksh93 s que : insere para bloquear os pressionamentos de tecla evapora assim que o shell preenche o valor de uma variável com sua entrada - você não pode colocar echo s em uma variável de shell (em qualquer shell, mas stty echo - caso em que ainda é configurável dessa forma) . Tanto quanto eu sei isso deve funcionar em qualquer shell - e definitivamente funciona em -echo , %code% e %code% . Pode até mesmo manipular de forma confiável a entrada multibyte - mas não vou jurar isso.

Na saída de demonstração acima, a saída real da função é precedida por cada gravação por outras informações que não são gravadas. Cada caractere exibido antes de cada primeiro ocorrendo %code% em qualquer uma das seqüências acima é realmente o %code% do terminal, como pode ser configurado com %code% ou %code% . O resto é o que é impresso como a saída da função - e é impresso, como você pode ver, assim que é digitado.

    
por 01.03.2015 / 05:14