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 dd
NUL
bytes. Portanto, quando você pressiona a seta para cima no teclado, dd
lê três bytes e grava 7 no próximo dd
NUL
- 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 dd
NUL
) conversões, dd
não sincroniza em unblock
NUL
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 dd
NUL
, itera sobre ele o byte para byte e o divide em um array dividido por zsh
NUL
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.