bash bash-completion

bash y readline: finalización de pestañas en un bucle de entrada de usuario?



bash-completion (5)

Estoy haciendo un script bash que presenta una línea de comando para el usuario.

El código cli es como esto:

#!/bin/bash cmd1() { echo $FUNCNAME: "$@" } cmd2() { echo $FUNCNAME: "$@" } cmdN() { echo $FUNCNAME: "$@" } __complete() { echo $allowed_commands } shopt -qs extglob fn_hide_prefix=''__'' allowed_commands="$(declare -f | sed -ne ''/^''$fn_hide_prefix''.* ()/!s/ ().*//p'' | tr ''/n'' '' '')" complete -D -W "this should output these words when you hit TAB" echo "waiting for commands" while read -ep"-> "; do history -s $REPLY case "$REPLY" in @(${allowed_commands// /|})?(+([[:space:]])*)) $REPLY ;; /?) __complete ;; *) echo "invalid command: $REPLY" ;; esac done

Aclaración: hecho y probado en Bash 4

Entonces, "leer -e" le da capacidades de lectura, puedo recordar comandos, editar la línea de entrada, etc. ¡ Lo que no puedo hacer de ninguna manera es tener la terminación de la pestaña de readline para trabajar!

Intenté dos cosas:

  1. Cómo se supone que debe hacerse: utilizando las instrucciones compiladas de bash "completa" y "compgen", que se informa que funcionan aquí. Actualización: no se informa que funcione en las secuencias de comandos .

  2. Esta fea solución

¿Por qué readline no se comporta correctamente cuando usa "completar" dentro del script? funciona cuando lo intento desde bash en modo interactivo ...


Bueno, parece que finalmente me quedé perplejo con la respuesta, y lamentablemente es así: en realidad no hay soporte completo para readline al interconectarlo a través de "read-e".

La respuesta la da el mantenedor de BASH, Chet Ramey. En este hilo , se aborda exactamente el mismo problema:

Estoy escribiendo un guión con un intérprete de línea de comandos y puedo trabajar la mayoría de las cosas (por ejemplo, historia, etc.) excepto por una cosa. La finalización del nombre de archivo funciona bien para algunos de los comandos, pero me gustaría utilizar otras opciones de finalización para otros. Funciona bien desde la línea de comando "real", pero no puedo hacer que funcione correctamente en mi ciclo "read -e, eval".

No podrás hacerlo. `read -e ''usa solo las terminaciones predeterminadas de readline.

Chet

Entonces, a menos que me pierda algo // solo // mientras golpea al programador el mecanismo de "lectura-e" como la media para una interfaz de usuario de CLI completa y correcta, la funcionalidad está paralizada, aunque el mecanismo subyacente (línea de lectura) funciona y se integra perfectamente con el resto de bash // end rant //

He expuesto la pregunta a la gente amable de #bash en freenode y se me sugirió probar con un contenedor de Readline como rlfe o rlwrap .

Finalmente, me puse en contacto con Chet por correo ayer, y confirmó que esto es por diseño, y que no tiene ganas de cambiarlo como el único caso de uso para la finalización programable en "leer", es decir, presentar una lista de comandos para el usuario de script, no parece una razón convincente para pasar tiempo trabajando en esto. Sin embargo, expresó que, en caso de que alguien realmente haga el trabajo, ciertamente miraría el resultado.

En mi humilde opinión, sin considerar la pena del esfuerzo, la capacidad de mostrar una CLI completa con solo 5 líneas de código, algo que un deseo era posible en muchos idiomas, es un error.

En este contexto, creo que la respuesta de Simon es brillante y está en su lugar. Intentaré seguir tus pasos y quizás con un poco de suerte obtendré más información. Ha pasado un tiempo desde que no pirateo en C sin embargo, y supongo que la cantidad de código que tendré que tomar para implementarlo no será trivial. Pero de todos modos lo intentaré.


Después de probar un script de finalización personalizado que sé que funciona (lo uso todos los días) y me encuentro con el mismo problema (cuando lo arreglé como el tuyo), decidí curiosear a través de la fuente 4.1 bash, y encontré este bloque interesante en bash-4.1/builtins/read.def:edit_line() :

old_attempted_completion_function = rl_attempted_completion_function; rl_attempted_completion_function = (rl_completion_func_t *)NULL; if (itext) { old_startup_hook = rl_startup_hook; rl_startup_hook = set_itext; deftext = itext; } ret = readline (p); rl_attempted_completion_function = old_attempted_completion_function; old_attempted_completion_function = (rl_completion_func_t *)NULL;

Parece que antes de llamar a readline() , restablece la función de finalización a nulo por alguna razón que solo una barba larga pirateo podría saber. Por lo tanto, hacer esto con la read integrada puede simplemente estar codificado para ser deshabilitado.

EDITAR : Un poco más sobre esto: el código de envoltura para detener la finalización en la read interna de read ocurrió entre bash-2.05a y bash-2.05b. Encontré esta nota en el archivo bash-2.05b/CWRU/changelog esa versión:

  • edit_line (llamado por read -e) ahora simplemente completa la finalización del nombre de archivo de readline estableciendo rl_attempted_completion_function en NULL, ya que, por ejemplo, completar la instrucción en la primera palabra de la línea no era realmente útil

Creo que es un legado de supervisión, y dado que la finalización programable ha recorrido un largo camino, lo que estás haciendo es útil. Tal vez puede pedirles que lo vuelvan a agregar, o simplemente que lo arreglen, si eso fuera factible por lo que están haciendo.

Temo que no tengo una solución diferente, aparte de lo que has encontrado hasta ahora, pero al menos sabemos por qué no funciona con la read .

EDIT2 : Bien, aquí hay un parche que acabo de probar que parece "funcionar". Pasa todas las pruebas de unidades y reg, y muestra este resultado de su secuencia de comandos cuando se ejecuta utilizando el parche bash, como esperaba:

$ ./tabcompl.sh waiting for commands -> **<TAB>** TAB hit output should these this when words you ->

Como verá, acabo de comentar esas 4 líneas y algunos códigos de temporizador para restablecer la función rl_attempted_completion_function cuando se especifica read -t y se produce un tiempo de espera, que ya no es necesario. Si va a enviar algo a Chet, es posible que desee rl_attempted_completion_function la totalidad de la basura rl_attempted_completion_function primero, pero al menos permitirá que su secuencia de comandos se comporte correctamente.

Parche:

--- bash-4.1/builtins/read.def 2009-10-09 00:35:46.000000000 +0900 +++ bash-4.1-patched/builtins/read.def 2011-01-20 07:14:43.000000000 +0900 @@ -394,10 +394,12 @@ } old_alrm = set_signal_handler (SIGALRM, sigalrm); add_unwind_protect (reset_alarm, (char *)NULL); +/* #if defined (READLINE) if (edit) add_unwind_protect (reset_attempted_completion_function, (char *)NULL); #endif +*/ falarm (tmsec, tmusec); } @@ -914,8 +916,10 @@ if (bash_readline_initialized == 0) initialize_readline (); +/* old_attempted_completion_function = rl_attempted_completion_function; rl_attempted_completion_function = (rl_completion_func_t *)NULL; +*/ if (itext) { old_startup_hook = rl_startup_hook; @@ -923,8 +927,10 @@ deftext = itext; } ret = readline (p); +/* rl_attempted_completion_function = old_attempted_completion_function; old_attempted_completion_function = (rl_completion_func_t *)NULL; +*/ if (ret == 0) return ret;

Tenga en cuenta que el parche bash tendría que ser distribuido o estar disponible de alguna manera donde sea que la gente use su script ...


Si va a hacer tanto esfuerzo, ¿por qué no simplemente agrega el costo de un tenedor o dos y utiliza algo que es más que capaz de proporcionar todo lo que desea? http://utopia.knoware.nl/~hlub/rlwrap/rlwrap.html

#!/bin/bash which yum && yum install rlwrap which zypper && zypper install rlwrap which port && port install rlwrap which apt-get && apt-get install rlwrap REPLY=$( rlwrap -o cat )

O como dice la página man:

En un script de shell, use rlwrap en modo ''one-shot'' como un reemplazo para leer

order=‘rlwrap −S ’Your pizza? ’−H past_orders −P Margherita −o cat‘


He estado luchando con el mismo problema desde hace un tiempo y creo que tengo una solución que funciona, en mi caso real, estoy usando compgen para generar posibles terminaciones. Pero aquí hay un ejemplo que ilustra la lógica central:

#!/bin/bash set -o emacs; tab() { READLINE_LINE="foobar" READLINE_POINT="${#READLINE_LINE}" } bind -x ''"/t":"tab"''; read -ep "$ ";

Establezca la opción emacs para habilitar el enlace de clave, enlace la tecla de tabulación a una función, cambie READLINE_LINE para actualizar la línea después del aviso y configure READLINE_POINT para reflejar la nueva longitud más larga de la línea.

En mi caso de uso, en realidad COMPREPLY variables COMP_WORDS, COMP_CWORD y COMPREPLY , pero esto debería ser suficiente para entender cómo agregar complementos personalizados al usar read -ep .

Debe actualizar READLINE_LINE para cambiar la línea de solicitud (coincidencia única de finalización), imprimir en las impresiones estándar antes de que se indique, ya que readline puso el terminal en modo raw y está capturando la entrada.


No estoy seguro de si esto responde exactamente a la pregunta de OP, pero estaba buscando qué comando podría uno usar, para obtener la finalización de la pestaña bash predeterminada de los comandos ejecutables conocidos (según $PATH ), como se muestra al presionar TAB . Desde que me llevaron a esta pregunta (que creo que está relacionada), pensé en publicar una nota aquí.

Por ejemplo, en mi sistema, escribir lua y luego TAB da:

$ lua<TAB> lua lua5.1 luac luac5.1 lualatex luatex luatools

Resulta que hay un bash incorporado (vea el comando # 949006 de Linux para enumerar todos los comandos y alias disponibles ), llamado compgen - y puedo alimentarlo con el mismo lua cadena que en el caso interactivo, y obtener los mismos resultados como si hubiera presionado TAB :

$ compgen -c lua luac lua5.1 lua luac5.1 luatex lualatex luatools

... y eso es exactamente lo que estaba buscando :)

Espero que esto ayude a alguien,
¡Aclamaciones!