Lire les clés spéciales en bash


8

Je joue avec un script qui, entre autres, liste une liste de sélection. Un péché:

1) Point 1               # (en surbrillance)
2) Point 2
3) Article 3 # (sélectionné)
4) Point 4

  • Lorsque l'utilisateur appuie sur down-arrowles éléments suivants est mis en surbrillance
  • Lorsque l'utilisateur appuie sur up-arrowles éléments précédents est mis en surbrillance
  • etc.
  • Lorsque l'utilisateur appuie sur l' tabélément est sélectionné
  • Lorsque l'utilisateur appuie sur shift+tabtous les éléments sont sélectionnés / désélectionnés
  • Lorsque l'utilisateur appuie sur ctrl+atous les éléments sont sélectionnés
  • ...

Cela fonctionne très bien à partir de l'utilisation actuelle, qui est mon utilisation personnelle où l'entrée est filtrée par ma propre configuration.

La question est de savoir comment rendre cela fiable sur différents terminaux.


J'utilise une solution un peu hackée pour lire l'entrée:

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

Etc.


Comme mentionné, la question est de savoir comment rendre cela fiable sur différents terminaux: c'est-à-dire quelles séquences d'octets définissent une clé spécifique. Est-ce même faisable en bash?

Une idée était d'utiliser soit tputou infocmpet de filtrer par le résultat donné par cela. Je suis cependant dans un hic là-bas car les deux tputet infocmpdiffèrent de ce que je lis réellement lorsque j'appuie sur les touches. Il en va de même par exemple en utilisant C sur 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)';
}

Les séquences de rendement se lisent comme défini par exemple linux, mais pas xterm, ce qui est défini par TERM.

Par exemple, flèche vers la gauche:

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

Qu'est-ce que je rate?


pas besoin de réinventer la roue, iselect le fait déjà. Alternativement, utilisez l'une des dialogvariantes, ou utilisez un langage avec un ncursessupport décent (perl ou python par exemple, si vous voulez vous en tenir aux langages de "scripting").
cas

1
Notez que la zshprise en charge des curses intégrées (dans le module zsh / curses) en plus des requêtes terminfo de base avec son echotitableau intégré et $terminfoassociatif.
Stéphane Chazelas

Réponses:


5

Ce qui vous manque, c'est que la plupart des descriptions de terminaux ( linuxen minorité ici, en raison de l'utilisation généralisée de chaînes codées en dur .inputrc) utilisent le mode d'application pour les clés spéciales. Cela rend les touches de curseur comme indiqué par tputet infocmpdiffère de ce que votre terminal (non initialisé) envoie. les applications curses initialisent toujours le terminal, et la base de données du terminal est utilisée à cet effet.

dialoga ses utilisations, mais ne répond pas directement à cette question. D'un autre côté, il est fastidieux (techniquement faisable , rarement fait ) de fournir une solution bash uniquement. En général, nous utilisons d'autres langues pour ce faire.

Le problème avec la lecture des clés spéciales est qu'elles sont souvent de plusieurs octets, y compris des caractères maladroits tels que escapeet ~. Vous pouvez le faire avec bash, mais vous devez ensuite résoudre le problème de déterminer de manière portable de quelle clé spéciale il s'agissait.

dialogles deux gèrent la saisie de touches spéciales et prennent le relais (temporairement) de votre écran. Si vous voulez vraiment un simple programme en ligne de commande, ce n'est pas le cas dialog.

Voici un programme simple en C qui lit une clé spéciale et l'imprime sous forme imprimable (et portable):

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

En supposant que cela ait été appelé tgetch, vous l'utiliseriez dans votre script comme ceci:

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

Lectures complémentaires:


Je vous remercie. Oui, inputrcc'était bien le coupable que je cherchais. Je dois le regarder un peu plus. J'ai envisagé d'utiliser python ou C, mais il est également amusant de pirater en tant que script bash. J'ai également essayé de jeter un œil à la source ncurses pour voir si je pouvais extraire les bits dont j'avais besoin - mais après un certain temps à creuser la source, je l'ai laissée sur la glace. Le «projet» a commencé comme une simple commande, puis est devenu un simple script interactif, puis a repris. Quelque part en cours de route, j'aurais dû utiliser une autre langue , mais je suis devenu un peu têtu (et comme mentionné, c'est amusant de pirater dans bash 2 :)
user367890

Trouvé les séquences, entre autres, /usr/share/doc/readline-common/inputrc.arrows. Comme j'ai déjà une fonction générique "read_key" que j'utilise dans le script, j'espérais qu'il y aurait un moyen plus simple de définir les séquences (dans le script) à partir de ce qui est réellement présenté lorsqu'une touche est enfoncée. C'est à dire similaire à l'extraction de définitions de infocmp. Mais ne le devinez pas et vous devez le laisser tel quel ou passer à une autre langue. Un compromis pourrait bien sûr consister à utiliser votre, joli, C-snippet. Mais alors je peux écrire le tout en C à la place. (Désolé pour le partage excessif.)
user367890

Est-ce le code C complet?
J'obtiens

Vous avez probablement omis le -lncurses, etc.
Thomas Dickey

6

Avez-vous essayé d'utiliser dialog? Il est livré en standard avec la plupart des distributions Linux et peut créer toutes sortes de boîtes de dialogue textuelles, y compris des listes de contrôle.

Par exemple:

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

Vous obtiendrez quelque chose comme ceci:

entrez la description de l'image ici

Et la sortie sera:

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

(ou selon les éléments que vous avez sélectionnés).

man dialog vous fournira des informations sur les autres types de boîtes de dialogue que vous pouvez créer et comment personnaliser l'apparence.


+1 pour l'effort, mais Dickey était plus au point de ce que je demande. En premier lieu, la question décrite - dans un sens plus général, la liste devait simplement donner un certain contexte. Deuxièmement, j'ai jeté un rapide coup d'œil à la boîte de dialogue - et, certes, je ne l'ai pas examinée à fond, mon cas, pour l'étendre, est une façade pour une base de données sqlite avec plusieurs milliers d'enregistrements où j'ai par exemple Page-Up / Down to faites défiler la sélection. Scroll-region, scroll-buffer, une ligne d'état, une ligne ex avec entrée modale, des sous-fonctions pour le filtrage, etc. En bref, cela peut sembler complexe, mais c'est plutôt simple…
user367890

… Mais le dialogue ne semblait pas tout à fait répondre aux besoins, ni être un peu lourd pour mon cas.
user367890

@ user367890 vos sons d'application comme un match parfait pour le Perl Curses, DBIet des DBD::SQLitemodules. ou leurs équivalents en python.
cas

@cas: Oui. J'ai écrit des applications similaires en utilisant python et C plus tôt - bien que je doive en réapprendre beaucoup. Ce "projet" est plus une aventure dans les possibilités bash et "pour le plaisir" :) Bien que je sois sur le point de l'abandonner ou de le porter dans une autre langue. Merci pour votre contribution.
user367890
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.