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:
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 .
¿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!