Leggi le chiavi speciali in bash


8

Sto giocando con una sceneggiatura che, tra le altre cose, elenca un elenco di selezione. Come in:

1) Articolo 1               # (evidenziato)
2) Articolo 2
3) Articolo 3 # (selezionato)
4) Articolo 4

  • Quando l'utente preme le down-arrowvoci successive viene evidenziato
  • Quando l'utente preme up-arrowle voci precedenti è evidenziato
  • eccetera.
  • Quando l'utente preme l' tabelemento è selezionato
  • Quando l'utente preme shift+tabtutte le voci sono selezionate / deselezionate
  • Quando l'utente preme ctrl+atutte le voci sono selezionate
  • ...

Funziona benissimo a partire dall'uso corrente, che è il mio uso personale in cui l'input è filtrato dalla mia configurazione.

La domanda è come renderlo affidabile su vari terminali.


Uso una soluzione in qualche modo hacker per leggere l'input:

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 così via.


Come accennato, la domanda è come renderlo affidabile su vari terminali: ovvero quali sequenze di byte definiscono una chiave specifica. È fattibile anche in bash?

Un pensiero era usare o tputo infocmpe filtrare in base al risultato dato da quello. Sono comunque in difficoltà in quanto entrambi tpute infocmpdifferiscono da ciò che leggo effettivamente premendo i tasti. Lo stesso vale per esempio usando C su 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)';
}

Le sequenze di rendimento vengono lette come definite per esempio linux, ma non xterm, che è ciò che è impostato da TERM.

Ad esempio freccia sinistra:

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

Cosa mi sto perdendo?


non è necessario reinventare la ruota, iselect lo fa già. In alternativa, utilizzare una delle dialogvarianti o utilizzare una lingua con un ncursessupporto decente (perl o python, ad esempio, se si desidera attenersi alle lingue di "scripting").
CAS

1
Si noti che zshha il supporto curses integrato (nel modulo zsh / curses) oltre alle query terminfo di base con il suo array echotiincorporato e $terminfoassociativo.
Stéphane Chazelas,

Risposte:


5

Quello che ti manca è che la maggior parte delle descrizioni dei terminali ( linuxè in minoranza qui, a causa dell'uso pervasivo di stringhe codificate in .inputrc) utilizza la modalità applicazione per tasti speciali. Ciò rende i tasti cursore come mostrato da tpute infocmpdifferisce da ciò che invia il tuo terminale (non inizializzato). le applicazioni maledizioni inizializzano sempre il terminale e la base dati del terminale viene utilizzata a tale scopo.

dialogha i suoi usi, ma non affronta direttamente questa domanda. D'altra parte, è ingombrante (tecnicamente fattibile , fatto raramente ) fornire una soluzione solo bash. Generalmente usiamo altre lingue per farlo.

Il problema con la lettura di chiavi speciali è che spesso sono più byte, inclusi caratteri scomodi come escapee ~. Puoi farlo con bash, ma poi devi risolvere il problema di determinare portabilmente quale chiave speciale fosse.

dialogentrambi gestisce l'immissione di tasti speciali e prende il controllo (temporaneamente) del display. Se vuoi davvero un semplice programma da riga di comando, non lo è dialog.

Ecco un semplice programma in C che legge una chiave speciale e la stampa in forma stampabile (e portatile):

#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;
}

Supponendo che questo sia stato chiamato tgetch, lo useresti nel tuo script in questo modo:

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

Ulteriori letture:


Grazie. Sì, inputrcera davvero il colpevole che stavo cercando. Devo guardarlo ancora. Ho pensato di usare Python o C, ma trovo divertente anche hackerarlo come uno script bash. Ho anche provato a dare un'occhiata alla fonte di ncurses per vedere se potevo estrarre i bit di cui avevo bisogno, ma dopo un po 'di tempo a scavare la fonte l'ho lasciata sul ghiaccio. Il "progetto" è iniziato come un semplice comando, poi è diventato un semplice script interattivo, per poi estenderlo nuovamente. Da qualche parte lungo la strada avrei dovuto andare in un'altra lingua , ma sono diventato un po 'testardo (e come detto è divertente hackerare in bash 2 :)
user367890

Trovato le sequenze, tra gli altri, /usr/share/doc/readline-common/inputrc.arrows. Dato che ho già una generica funzione "read_key" che utilizzo nello script, ho sperato che ci fosse un modo più semplice per definire le sequenze (nello script) da ciò che viene effettivamente presentato quando si preme un tasto. Cioè simile a estrarre definizioni da infocmp. Ma non indovinare e non devi lasciarlo così com'è o passare a un'altra lingua. Ovviamente un compromesso potrebbe essere l'uso del tuo, simpatico, frammento C. Ma poi posso scrivere tutto in C invece. (Ci scusiamo per il oversharing.)
user367890

È quello il codice C completo? Ricevo una dozzina di errori quando provo a compilare questo usando gcc su Debian 9
InterLinked

Probabilmente hai omesso il -lncurses, ecc.
Thomas Dickey il

6

Hai provato a usare dialog? Viene fornito di serie con la maggior parte delle distribuzioni Linux e può creare tutti i tipi di finestre di dialogo basate su testo, comprese le liste di controllo.

Per esempio:

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

Otterrai qualcosa del genere:

inserisci qui la descrizione dell'immagine

E l'output sarà:

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

(o qualsiasi elemento selezionato).

man dialog ti fornirà informazioni sugli altri tipi di finestre di dialogo che puoi creare e su come personalizzare l'aspetto.


+1 per lo sforzo, ma Dickey era più al punto di quello che sto chiedendo. Per uno a quello che era il problema descritto - in senso più generale, l'elenco doveva semplicemente fornire un contesto. In secondo luogo ho dato una rapida occhiata alla finestra di dialogo - e devo ammettere che non l'ho esaminata a fondo, il mio caso, per espanderlo, è un fronte per un database sqlite con diverse migliaia di record in cui ho ad esempio Page-Up / Down to scorrere la selezione. Scroll-region, scroll-buffer, una linea di stato, una linea ex con input modale, funzioni secondarie per il filtraggio ecc. In breve, può sembrare complessa, ma è piuttosto semplice ...
user367890

... ma il dialogo non sembra del tutto soddisfare i bisogni o essere un po 'ingombrante per il mio caso.
user367890,

@ user367890 i suoni di applicazione come un partner perfetto per il Perl Curses, DBIe DBD::SQLitemoduli. o i loro equivalenti in pitone.
CAS

@cas: Sì. Ho scritto applicazioni simili usando Python e C in precedenza, anche se devo imparare di nuovo molto. Questo "progetto" è più un'avventura nelle possibilità bash e "per divertimento" :) Anche se mi sto avvicinando ad abbandonarlo o portarlo in un'altra lingua. Grazie per l'input.
user367890,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.