Leia chaves especiais no bash

8

Estou brincando com um script que, entre outras coisas, lista uma lista de seleção. Como em:

1) Item 1               # (destacado)
2) Item 2
3) Item 3 # (selecionado)
4) Item 4

  • Quando o usuário pressionar os down-arrowpróximos itens, será destacado
  • Quando o usuário pressiona up-arrowos itens anteriores é destacado
  • etc.
  • Quando o tabitem pressionado pelo usuário é selecionado
  • Quando o usuário pressiona shift+tabtodos os itens são selecionados / desmarcados
  • Quando o usuário pressiona ctrl+atodos os itens são selecionados
  • ...

Isso funciona bem como no uso atual, que é meu uso pessoal, onde as entradas são filtradas pela minha própria configuração.

A questão é como tornar isso confiável em vários terminais.


Eu uso uma solução um pouco hackish para ler a entrada:

while read -rsn1 k # Read one key (first byte in key press)
do
    case "$k" in
    [[:graph:]])
        # Normal input handling
        ;;
    $'\x09') # TAB
        # Routine for selecting current item
        ;;
    $'\x7f') # Back-Space
        # Routine for back-space
        ;;
    $'\x01') # Ctrl+A
        # Routine for ctrl+a
        ;;
    ...
    $'\x1b') # ESC
        read -rsn1 k
        [ "$k" == "" ] && return    # Esc-Key
        [ "$k" == "[" ] && read -rsn1 k
        [ "$k" == "O" ] && read -rsn1 k
        case "$k" in
        A) # Up
            # Routine for handling arrow-up-key
            ;;
        B) # Down
            # Routine for handling arrow-down-key
            ;;
        ...
        esac
        read -rsn4 -t .1 # Try to flush out other sequences ...
    esac
done

E assim por diante.


Como mencionado, a questão é como tornar isso confiável em vários terminais: ou seja, quais seqüências de bytes definem uma chave específica. É mesmo possível no bash?

Um pensamento era usar um tputou infocmpe filtrar pelo resultado dado por isso. Estou, no entanto, em uma senão lá como tanto tpute infocmpdiferem do que eu realmente ler quando na verdade pressionando as teclas. O mesmo vale, por exemplo, usando C sobre bash.

for t in $(find /lib/terminfo -type f -printf "%f\n"); { 
    printf "%s\n" "$t:"; 
    infocmp -L1 $t | grep -E 'key_(left|right|up|down|home|end)';
}

Sequências de rendimento lidas como definidas, por exemplo linux, mas não xterm, e é isso que é definido por TERM.

Por exemplo, seta para a esquerda:

  • tput/ infocmp:\x1 O D
  • read: \x1 [ D

o que estou perdendo?

user367890
fonte
não há necessidade de reinventar a roda, o iselect já faz isso. Como alternativa, use uma das dialogvariantes ou use uma linguagem com ncursessuporte decente (perl ou python, por exemplo, se você quiser usar as linguagens de "script").
10286
1
Observe que ele zshpossui suporte a curses integrados (no módulo zsh / curses), além da consulta básica de terminfo com sua matriz incorporada echotie $terminfoassociativa.
Stéphane Chazelas

Respostas:

5

O que você está perdendo é que a maioria das descrições de terminal (que linuxé minoria aqui, devido ao uso generalizado de cadeias codificadas .inputrc) usa o modo de aplicativo para teclas especiais. Isso faz com que as teclas do cursor sejam mostradas tpute infocmpsejam diferentes do que o seu terminal (não inicializado) envia. Os aplicativos amaldiçoados sempre inicializam o terminal e a base de dados do terminal é usada para esse fim.

dialogtem seus usos, mas não aborda diretamente essa questão. Por outro lado, é complicado (tecnicamente factível , raramente executado ) fornecer uma solução apenas para o bash. Geralmente usamos outros idiomas para fazer isso.

O problema com a leitura de chaves especiais é que elas geralmente têm vários bytes, incluindo caracteres estranhos como escapee ~. Você pode fazer isso com o bash, mas precisará resolver o problema de determinar portabilidade que chave especial era essa.

dialogambos manipulam a entrada de teclas especiais e assumem (temporariamente) o seu monitor. Se você realmente deseja um programa simples de linha de comando, não é dialog.

Aqui está um programa simples em C que lê uma chave especial e a imprime na forma imprimível (e portátil):

#include <curses.h>

int
main(void)
{   
    int ch;
    const char *result;
    char buffer[80];

    filter();
    newterm(NULL, stderr, stdin);
    keypad(stdscr, TRUE);
    noecho();
    cbreak();
    ch = getch();
    if ((result = keyname(ch)) == 0) {
        /* ncurses does the whole thing, other implementations need this */
        if ((result = unctrl((chtype)ch)) == 0) {
            sprintf(buffer, "%#x", ch);
            result = buffer;
        }
    }
    endwin();
    printf("%s\n", result);
    return 0;
}

Supondo que isso fosse chamado tgetch, você o usaria em seu script assim:

case $(tgetch 2>/dev/null) in
KEY_UP)
   echo "got cursor-up"
   ;;
KEY_BACKSPACE|"^H")
   echo "got backspace"
   ;;
esac

Leitura adicional:

Thomas Dickey
fonte
Obrigado. Sim, inputrcera realmente o culpado que eu estava procurando. Tem que olhar um pouco mais. Já considerei usar python ou C, mas acho divertido invadir também como um script do bash. Também tentei dar uma olhada na fonte ncurses para ver se conseguia extrair os bits necessários - mas depois de algum tempo cavando a fonte, a deixei no gelo. O "projeto" começou como um comando simples, tornou-se um script interativo simples e depois estendeu-o novamente. Em algum lugar ao longo do caminho eu deveria ter ido outra língua , mas tem um teimoso bit (e como mencionado é divertido de se cortar em em bash 2 :)
user367890
Encontrou as seqüências em, entre outras /usr/share/doc/readline-common/inputrc.arrows. Como eu já tenho uma função genérica "read_key" que uso no script, esperava que houvesse uma maneira mais fácil de definir as seqüências (no script) do que é realmente apresentado quando uma tecla é pressionada. Ou seja, semelhante à extração de definições de infocmp. Mas acho que não, e você tem que deixá-lo como está ou seguir para outro idioma. Obviamente, um compromisso seria usar seu bom fragmento C. Mas então eu posso escrever a coisa toda em C. (Desculpe pelo
excesso de compartilhamento
Esse é o código C completo? Eu recebo cerca de uma dúzia de erros quando tento compilar isso usando o gcc no Debian 9
InterLinked
Você provavelmente omitiu o -lncursesetc.
Thomas Dickey
6

Você já tentou usar dialog? Ele é padrão na maioria das distribuições Linux e pode criar todos os tipos de caixas de diálogo baseadas em texto, incluindo listas de verificação.

Por exemplo:

exec 3>&1 # open temporary file handle and redirect it to stdout

#                           type      title        width height n-items    
items=$(dialog --no-lines --checklist "Title here" 20    70     4 \
          1 "Item 1" on \
          2 "Item 2" off \
          3 "Item 3" on \
          4 "Item 4" off \
            2>&1 1>&3) # redirect stderr to stdout to catch output, 
                       # redirect stdout to temporary file
selected_OK=$? # store result value
exec 3>&- # close new file handle 

# handle output
if [ $selected_OK = 0 ]; then
    echo "OK was selected."
    for item in $items; do
        echo "Item $item was selected."
    done
else
    echo "Cancel was selected."
fi

Você obterá algo como isto:

insira a descrição da imagem aqui

E a saída será:

 OK was selected.
 Item 1 was selected.
 Item 3 was selected.

(ou quaisquer itens que você selecionou).

man dialog fornece informações sobre os outros tipos de caixas de diálogo que você pode criar e sobre como personalizar a aparência.

marinus
fonte
+1 por esforço, mas Dickey foi mais ao ponto do que estou perguntando. Por um lado, qual era a questão descrita - em um sentido mais geral, a lista era apenas para dar algum contexto. Em segundo lugar, observei rapidamente o diálogo - e reconhecidamente não o analisei minuciosamente; meu caso, para expandi-lo, é uma frente para um banco de dados sqlite com vários milhares de registros onde, por exemplo, Page-Up / Down to rolagem através da seleção. Scroll-região, rolagem-buffer, uma linha de status, uma linha ex com a entrada modal, sub funções para filtragem etc. Em suma, pode parecer complexo, mas é bastante simples ...
user367890
... mas o diálogo não pareceu atender às necessidades ou ser um tanto complicado para o meu caso.
user367890
@ user367890 seus sons de aplicação como uma combinação perfeita para o perl Curses, DBIe DBD::SQLitemódulos. ou seus equivalentes python.
12286
@cas: Sim. Escrevi aplicativos semelhantes usando python e C anteriormente - embora eu tenha que reaprender muito disso. Esse "projeto" é mais uma aventura para as possibilidades do bash e "para diversão" :) Embora eu esteja chegando perto de abandoná-lo ou portá-lo para outro idioma. Obrigado pela contribuição.
user367890