Leer teclas especiales en bash

8

Estoy jugando con un guión que, entre otras cosas, enumera una lista de selección. Como en:

1) Elemento 1               # (resaltado)
2) Artículo 2
3) Elemento 3 # (seleccionado)
4) Artículo 4

  • Cuando el usuario presiona los down-arrowsiguientes elementos se resalta
  • Cuando el usuario presiona up-arrowlos elementos anteriores se resalta
  • etc.
  • Cuando el usuario presiona el tabelemento está seleccionado
  • Cuando el usuario presiona shift+tabtodos los elementos están seleccionados / deseleccionados
  • Cuando el usuario presiona ctrl+atodos los elementos están seleccionados
  • ...

Esto funciona bien a partir del uso actual, que es mi uso personal donde mi propia configuración filtra la entrada.

La pregunta es cómo hacer que esto sea confiable en varios terminales.


Yo uso una solución algo hack para leer la 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

Y así.


Como se mencionó, la pregunta es cómo hacer que esto sea confiable en varios terminales: es decir, qué secuencias de bytes definen una clave específica. ¿Es factible incluso en bash?

Un pensamiento era usar uno tputo infocmpy filtrar por el resultado dado por eso. Sin embargo estoy en un obstáculo que hay que tanto tputy infocmpdifieren de lo que en realidad leo cuando en realidad la pulsación de teclas. Lo mismo ocurre, por ejemplo, con 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)';
}

Las secuencias de rendimiento se leen como se define, por ejemplo linux, pero no xterm, que es lo que establece TERM.

Por ejemplo, flecha izquierda:

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

¿Qué me estoy perdiendo?

usuario367890
fuente
no es necesario reinventar la rueda, iselect ya lo hace. Alternativamente, use una de las dialogvariantes, o use un lenguaje con ncursessoporte decente (perl o python, por ejemplo, si desea seguir con los lenguajes de "scripting").
cas
1
Tenga en cuenta que zshtiene soporte para maldiciones incorporadas (en el módulo zsh / curses) además de consultas básicas de terminfo con su matriz echotiincorporada y $terminfoasociativa.
Stéphane Chazelas

Respuestas:

5

Lo que falta es que la mayoría de las descripciones de terminal ( linuxes una minoría aquí, debido al uso generalizado de cadenas codificadas .inputrc) usan el modo de aplicación para teclas especiales. Eso hace que las teclas de cursor como se muestran tputy infocmpdifieran de lo que envía su terminal (sin inicializar). las aplicaciones curses siempre inicializan el terminal, y la base de datos del terminal se usa para ese propósito.

dialogtiene sus usos, pero no aborda directamente esta pregunta. Por otro lado, es engorroso (técnicamente factible , rara vez se hace ) proporcionar una solución de solo bash. Generalmente usamos otros idiomas para hacer esto.

El problema con la lectura de claves especiales es que a menudo son múltiples bytes, incluidos caracteres incómodos como escapey ~. Puede hacer esto con bash, pero luego debe resolver el problema de determinar de forma portátil qué clave especial era esta.

dialogambos manejan la entrada de teclas especiales y se hacen cargo (temporalmente) de su pantalla. Si realmente quiere un programa simple de línea de comandos, no lo es dialog.

Aquí hay un programa simple en C que lee una clave especial y la imprime en forma imprimible (y 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;
}

Suponiendo que se llamara a esto tgetch, lo usaría en su script de esta manera:

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

Otras lecturas:

Thomas Dickey
fuente
Gracias. Sí, inputrcfue el culpable que estaba buscando. Hay que mirarlo un poco más. He considerado ir a Python o C, pero también resulta divertido piratearlo como un script bash. También intenté echar un vistazo a la fuente ncurses para ver si podía extraer los bits que necesitaba, pero después de bastante tiempo cavando la fuente, la dejé en hielo. El "proyecto" comenzó como un comando simple, luego se convirtió en un script interactivo simple y luego se extendió nuevamente. En algún lugar del camino, debería haber ido a otro idioma , pero me puse un poco terco (y como se mencionó, es divertido hackearlo en bash 2 :)
user367890
Encontramos las secuencias en, entre otros /usr/share/doc/readline-common/inputrc.arrows,. Como ya tengo una función genérica "read_key" que utilizo en todo el script, esperaba que hubiera una manera más fácil de definir las secuencias (en el script) de lo que realmente se presenta cuando se presiona una tecla. Es decir, similar a extraer definiciones de infocmp. Pero suponga que no, y que tenga que dejarlo como está o pasar a otro idioma. Un compromiso podría ser, por supuesto, usar su, agradable, C-snippet. Pero luego puedo escribir todo en C en su lugar. (Perdón por
compartir demasiado
¿Es ese el código C completo? Recibo una docena de errores cuando intento compilar esto usando gcc en Debian 9
InterLinked
Probablemente omitiste el -lncurses, etc.
Thomas Dickey
6

¿Has intentado usar dialog? Viene de manera estándar con la mayoría de las distribuciones de Linux y puede crear todo tipo de cuadros de diálogo basados ​​en texto, incluidas las listas de verificación.

Por ejemplo:

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

Obtendrás algo como esto:

ingrese la descripción de la imagen aquí

Y la salida será:

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

(o los elementos que haya seleccionado).

man dialog obtendrá información sobre los otros tipos de diálogos que puede crear y cómo personalizar la apariencia.

marinus
fuente
+1 por esfuerzo, pero Dickey fue más al punto de lo que estoy preguntando. Por un lado, lo que describió el problema fue: en un sentido más general, la lista era simplemente para dar un poco de contexto. En segundo lugar, he echado un vistazo rápido al cuadro de diálogo, y es cierto que no lo he examinado a fondo, mi caso, para ampliarlo, es el frente de una base de datos sqlite con varios miles de registros donde tengo, por ejemplo, Re Pág / Arriba / Abajo desplazamiento a través de la selección. Scroll-region, scroll-buffer, una línea de estado, una línea ex con entrada modal, subfunciones para filtrado, etc. En resumen, puede sonar complejo, pero es bastante simple ...
user367890
... pero el diálogo no parecía satisfacer las necesidades, o era algo engorroso para mi caso.
user367890
@ user367890 sus sonidos de aplicaciones como un complemento perfecto para el Perl Curses, DBIy DBD::SQLitemódulos. o sus equivalentes en pitón.
cas
@cas: sí. He escrito aplicaciones similares usando Python y C anteriormente, aunque tengo que volver a aprender mucho. Este "proyecto" es más una aventura en las posibilidades de bash y "por diversión" :) Aunque me estoy acercando a abandonarlo o portarlo a otro idioma. Gracias por tu aporte.
user367890