En varias ocasiones he hablado de las bondades y la potencia que ofrece bash; ya sea mediante trucos, scripts u otros puntos interesantes... La cuestión está que la mayoría de las veces lo he expuesto como un entorno muy minimalista; en el sentido de que suelo mostrar un texto en la terminal que lee un valor, lo procesa y realiza una serie de acciones ya definidas en el código. Esto está bastante bien y cumple su función, pero a veces necesitamos de un entorno más interactivo o más vistoso, ya que hay que reconocer que aunque la terminal es muy fascinante, no ofrece un aspecto precisamente moderno... En los lenguajes de programación de alto nivel se suelen usar las famosas GUI (Graphical User Interface) pero esto en bash no es una opción; por ello existe una versión más simplificada de éstas: Las TUI (Text User Interface).
Una TUI no es ni más ni menos que una interfaz gráfica basada en texto plano; sin florituras ni imágenes ni efectos gráficos. Esto puede parecer burdo, pero lo cierto es que mejora gratamente la experiencia del usuario, especialmente si hablamos de menús interactivos; es por ello que suelen ser usados en entornos en los que queremos que el script interactue con un usuario que quiera ejecutar el contenido del script de forma simple e intuitiva.
Para lograr el resultado deseado requeriremos tener previamente instalada una herramienta que servirá de base para todo lo que trataremos de aquí en adelante. Dicha herramienta se denomina dialog y su objetivo es mostrar una ventana emergente que mostrará una serie de opciones previamente establecidas por nosotros. Dicha herramienta no está instalada por defecto pero al estar incluida en los repositorios oficiales la instalación sería tan sencilla como recurrir a apt o yum (según el sistema que usemos); algo como:
Toda interfaz está compuesta por distintas partes que serán comunes para cualquier tipo de éstas... Es decir que aunque los distintos tipos de interfaces tienen sus diferencias, siguen el mismo tipo de estructura y se podría decir que está dividido en varias partes:
Si juntásemos todos estos ejemplos, un esqueleto que serviría de plantilla para la mayoría de las ocasiones sería:
Obviamente esto es un esqueleto que no valdría para un caso real, ya que no hemos especificado qué tipo de interfaz se trata ni tampoco hemos visto cuales son las posibilidades que nos ofrece dicha parte... Cómo la mayoría de los tipos difieren bastante entre sí a nivel de sintaxis y estructura, lo mejor es ver cada uno de éstos uno por uno; aprovechando el esqueleto que recién acabamos de crear.
Una TUI no es ni más ni menos que una interfaz gráfica basada en texto plano; sin florituras ni imágenes ni efectos gráficos. Esto puede parecer burdo, pero lo cierto es que mejora gratamente la experiencia del usuario, especialmente si hablamos de menús interactivos; es por ello que suelen ser usados en entornos en los que queremos que el script interactue con un usuario que quiera ejecutar el contenido del script de forma simple e intuitiva.
Para lograr el resultado deseado requeriremos tener previamente instalada una herramienta que servirá de base para todo lo que trataremos de aquí en adelante. Dicha herramienta se denomina dialog y su objetivo es mostrar una ventana emergente que mostrará una serie de opciones previamente establecidas por nosotros. Dicha herramienta no está instalada por defecto pero al estar incluida en los repositorios oficiales la instalación sería tan sencilla como recurrir a apt o yum (según el sistema que usemos); algo como:
- apt-get install dialog
Con la herramienta instalada ya podríamos empezar a crear nuestras interfaces de texto, pero no es tan sencillo como escribir dialog y programar con normalidad... Es necesario entender algunos conceptos antes de comenzar a crear estas interfaces pues en caso contrario las interfaces probablemente no muestren nada. Una opción podría ser el comando man sobre dialog con el fin de obtener la información más detallada posible, si bien con tener unas nociones generales sería suficiente para desenvolvernos en la creación de este tipo de interfaces. Lo primero y fundamental sería entender las bases que componen cualquier "dialog".
Bases:
Bases:
Toda interfaz está compuesta por distintas partes que serán comunes para cualquier tipo de éstas... Es decir que aunque los distintos tipos de interfaces tienen sus diferencias, siguen el mismo tipo de estructura y se podría decir que está dividido en varias partes:
- Interfaz: La primera parte es la encargada de llamar a la herramienta dialog; encargada de mostrar la interfaz de texto que usaremos a partir de ahora... Dicho llamamiento va habitualmente seguido del parámetro --clear. El parámetro --clear serviría para "limpiar" el contenido mostrado en la consola y mostrar únicamente nuestra interfaz; además para tener todo mejor estructurado podemos usar como apoyo el carácter \ para tener todo estructurado en distintas líneas que nos facilitarán su interpretación. Dicho carácter iría al final de cada línea y teniendo en cuenta dicha inclusión el primera parte quedaría así:
- dialog --clear \
- Título: Aún con la terminal limpiada y la interfaz preparada, hemos de tener en cuenta que toda interfaz posee un título descriptivo que aparecerá en la parte superior de éstas; éste es implantado haciendo uso del parámetro --title sumado del título en cuestión. Al no ser la última línea (ya que queremos tener todo estructurado línea por línea) incluiríamos el carácter \ al final de la sección, con lo escribiríamos:
- --tittle "Prueba TUI" \
- Tipo de interfaz: La parte más importante en la que cada tipo posee su propia estructura. Basta decir que siempre sería la tercera parte y que dependiendo de lo que escojamos, nuestro dialog tendrá un comportamiento distinto. Este punto será explicado en profundidad más adelante.
- Tamaño de la TUI: El último parámetro, y no por ello menos importante sería el tamaño de la interfaz... No importa lo bien que hayamos declarado los anteriores parámetros, es importante declarar un tamaño adecuado si no queremos que el texto que vayamos a mostrar se nos acople. El tamaño de ésta suele ir al final y es asignado mediante dos parámetros numéricos: El primer número constaría de el número de líneas que ocuparía la TUI; es decir, el tamaño vertical que tendría; mientras que el segundo número, el cual estaría separado del primero por un espacio en blanco, especificaría el número de caracteres que ocuparía la interfaz... Es decir el tamaño horizontal que ocuparía. En caso de quedarnos cortos con estos valores nos quedaría una interfaz desagradable y aparatosa de usar, con lo que siempre es recomendable establecer este valor al alza. Un ejemplo de dos valores válidos para esta parte sería: 6 30.
- #!/bin/bash
- dialog --clear \
- --title "Prueba TUI" \
- --tipo ... \
- 6 30
Obviamente esto es un esqueleto que no valdría para un caso real, ya que no hemos especificado qué tipo de interfaz se trata ni tampoco hemos visto cuales son las posibilidades que nos ofrece dicha parte... Cómo la mayoría de los tipos difieren bastante entre sí a nivel de sintaxis y estructura, lo mejor es ver cada uno de éstos uno por uno; aprovechando el esqueleto que recién acabamos de crear.
Mensaje de texto
Con diferencia la opción más sencilla y usada de todas. Esto no es más que un mensaje de texto, comúnmente denominado message box, el cual simplemente habría que leer y presionar la tecla "INTRO" una vez leído el mensaje. Para crear dicho mensaje haría falta muy poca cantidad de código, ya que simplemente habría que escribir en la parte del tipo el parámetro --msgbox seguido del texto que deseásemos mostrar. Es decir que si quisiésemos mostrar el mensaje "Esto es un mensaje de texto" usando como base nuestro anterior código, tendríamos el siguiente resultado:
Cuadro de texto
El nivel de dificultad de este comando es muy similar al de nuestro anterior ejemplo, con la diferencia de que en este caso es importante calcular un tamaño adecuado para nuestra interfaz para hacer que el resultado aparezca de forma integra y sin interrupciones. El objetivo de esta opción es mostrar el contenido de un fichero; algo parecido a lo que hace cat pero mostrado de forma más vistosa. El llamamiento a esta opción se realiza mediante la utilización del parámetro --textbox seguido de la ruta completa del fichero que queremos consultar; Con lo que la diferencia con respecto a nuestro anterior ejemplo es muy leve:
Cuadro de información
Esta opción es también muy simple pero no es tan útil como el resto, en mi opinión, ya que se trata de un simple marco informativo que no permite que el usuario interactue con él; es decir que no importa lo que el usuario final realice que la interfaz permanecerá inamovible... Por lo general estas interfaces van a acompañadas por un sleep (espera) de x segundos; y el objetivo de éstas es mantener al usuario final en espera. Este cuadro se puede utilizar gracias al parámetro --infobox; una pega que tienen esta opción es que, por motivos que desconozco, no es compatible con el parámetro --clear declarado al principio, ya que este último elimina lo mostrado por el infobox (aún cuando la limpieza de pantalla ha sido declarada primero). Esto alteraría ligeramente nuestra plantilla inicial y haría que tuviesemos un resultado parecido al siguiente:
Si/No
A veces necesario recurrir a un entorno que requiera la intervención del usuario... Ya sea para aceptar o denegar una cosa o simplemente para preguntar si realmente desea ejecutar una acción delicada (como por ejemplo eliminar un fichero). Esto no es posible con los cuadros de texto, ya que simplemente te muestra un mensaje y punto; es por ello que para lograr éste objetivo habría que pasar al siguiente nivel: Los cuadros de dialogo Si/No. Este tipo de TUI es creado mediante el parámetro --yesno, a continuación del texto o pregunta que se quiera realizar a continuación. Esto de por sí puede parecer suficiente, pero en verdad no es así ya que es necesario volcar la respuesta sobre una variable para luego procesar dicha respuesta.
El volcado de la respuesta es tan sencillo como añadir una variable después de "dialog" creado y asignarle el valor $?. Dicho valor puede parecer un sinsentido, pero la función de dicho valor es almacenar la respuesta enviada por nosotros... Valor enviado cuando hemos hecho la selección. El valor que la variable puede recibir puede ser uno de estos tres:
- #!/bin/bash
- dialog --clear \
- --title "Prueba TUI" \
- --msgbox "Esto es un mensaje de texto" \
- 6 30
Cuadro de texto
El nivel de dificultad de este comando es muy similar al de nuestro anterior ejemplo, con la diferencia de que en este caso es importante calcular un tamaño adecuado para nuestra interfaz para hacer que el resultado aparezca de forma integra y sin interrupciones. El objetivo de esta opción es mostrar el contenido de un fichero; algo parecido a lo que hace cat pero mostrado de forma más vistosa. El llamamiento a esta opción se realiza mediante la utilización del parámetro --textbox seguido de la ruta completa del fichero que queremos consultar; Con lo que la diferencia con respecto a nuestro anterior ejemplo es muy leve:
- #!/bin/bash
- dialog --clear \
- --title "Prueba TUI" \
- --textbox "/etc/issue" \
- 6 30
Cuadro de información
Esta opción es también muy simple pero no es tan útil como el resto, en mi opinión, ya que se trata de un simple marco informativo que no permite que el usuario interactue con él; es decir que no importa lo que el usuario final realice que la interfaz permanecerá inamovible... Por lo general estas interfaces van a acompañadas por un sleep (espera) de x segundos; y el objetivo de éstas es mantener al usuario final en espera. Este cuadro se puede utilizar gracias al parámetro --infobox; una pega que tienen esta opción es que, por motivos que desconozco, no es compatible con el parámetro --clear declarado al principio, ya que este último elimina lo mostrado por el infobox (aún cuando la limpieza de pantalla ha sido declarada primero). Esto alteraría ligeramente nuestra plantilla inicial y haría que tuviesemos un resultado parecido al siguiente:
- #!/bin/bash
- dialog \
- --infobox "Espere 4 segundos" \
- 6 30
- sleep 4
Si/No
A veces necesario recurrir a un entorno que requiera la intervención del usuario... Ya sea para aceptar o denegar una cosa o simplemente para preguntar si realmente desea ejecutar una acción delicada (como por ejemplo eliminar un fichero). Esto no es posible con los cuadros de texto, ya que simplemente te muestra un mensaje y punto; es por ello que para lograr éste objetivo habría que pasar al siguiente nivel: Los cuadros de dialogo Si/No. Este tipo de TUI es creado mediante el parámetro --yesno, a continuación del texto o pregunta que se quiera realizar a continuación. Esto de por sí puede parecer suficiente, pero en verdad no es así ya que es necesario volcar la respuesta sobre una variable para luego procesar dicha respuesta.
El volcado de la respuesta es tan sencillo como añadir una variable después de "dialog" creado y asignarle el valor $?. Dicho valor puede parecer un sinsentido, pero la función de dicho valor es almacenar la respuesta enviada por nosotros... Valor enviado cuando hemos hecho la selección. El valor que la variable puede recibir puede ser uno de estos tres:
- 0 --> Hemos seleccionado Si.
- 1 --> Hemos seleccionado No.
- 255 --> Hemos pulsado la tecla [Esc].
Con esta información y con nuestro "esqueleto" podríamos crear el siguiente código con el que podríamos sacar el máximo partido a esta opción:
- #!/bin/bash
- dialog --clear \
- --title "Prueba TUI" \
- --yesno "Seguro que quieres borrar el archivo /tmp/prueba.txt?" \
- 6 30
- respuesta=$?
- case $respuesta in
- 0) echo "Hemos seleccionado Si";;
- 1) echo "Hemos seleccionado No";;
- 255) echo "Hemos pulsado la tecla [Esc]";;
- esac
Cuadro de entrada de texto o Inputbox
Siguiendo por la línea del volcado de valores, tendríamos algo menos cerrado que la opción anterior... Cuando queremos limitarnos a acciones de sí o no, la anterior opción se ajusta a nuestras necesidades a la perfección; en cambio si quisiésemos meter un valor completamente definido por nosotros, ya sea un número o un texto, deberíamos recurrir a una opción más flexible: los inputboxes. Dicho tipo de dialog se declaran mediante el parámetro --inputbox seguido del texto que querríamos mostrar a continuación...
La cuestión está en que a diferencia de con nuestro anterior caso, aquí el volcado de datos cambia ligeramente ya que no se realizaría sobre una variable, sino sobre un fichero... Cosa que cambia ligeramente el tratamiento del volcado de datos. En este caso para volcar los datos habría que escribirlo después del texto que queremos mostrar y con la estructura 2>fichero_de_destino. Con el valor volcado en dicho fichero, habría que consultar éste para cualquier interacción con el valor introducido... Cosa muy útil cuando estemos tratando con el valor en cuestión, pero es recomendable eliminar dicho fichero tras interactuar con éste para evitar fugas de información y ocupación innecesaria de espacio en el disco duro.
Dicho esto, un script válido que respetaría estos principios sería:
- #!/bin/bash
- FICHERO='/tmp/input.txt'
- dialog --clear \
- --title "Prueba TUI" \
- --inputbox "Introduzca el texto" 2>${FICHERO} \
- 6 30
- echo ''
- cat ${FICHERO}
- echo ''
- rm ${FICHERO}
Menú interactivo
En mi opinión se trata de la opción más interesante que ofrece este tipo de interfaz. Queremos poner a disposición del usuario una serie de opciones predefinidas... Sin la intervención de interfaces tendríamos que hacer que el usuario escribiese las elecciones a mano y esperar a que la elección realizada coincida con aquellas que nosotros somos capaces de procesar... Aquí eliminamos dicha posibilidad de error y limitamos al usuario a una serie de opciones que nosotros ya hemos predispuesto para él; opciones que podemos escoger mediante las flechas de dirección del teclado sin problema alguno.
Las ventajas que ofrece este menú son obvias, es por ello que es una de las opciones más usadas por aquellos que optan por presentar sus scripts en TUIs para los usuarios.
Para llamar a un menú se requiere del uso del parámetro --menu; pero es necesario incluir una serie de características que harán posible la aparición de éste... Para empezar, en el establecimiento del tamaño de la interfaz, hemos de especificar el número exacto de elementos que aparecerán en el menú; eso significaría que dicha sección constaría ahora de tres parámetros, entre los cuales el número de elementos del menú sería el último de éstos tres.
Después de establecer dicho tamaño habría que imponer los elementos del menú con la siguiente estructura:
Nombre_elemento) Texto a mostrar \
El texto a mostrar sería lo representación que aparecería en pantalla mientras que el nombre que se establece antes del caracter ) sería el nombre del elemento que seleccionaríamos en el menú... Esta línea habría que repetirla tantas veces como elementos queramos incluir; a excepción del último que variaría ligeramente, en vez de establecer el carácter \ habría que especificar el fichero en el que volcaríamos nuestro resultado.
Nombre_elemento) Texto a mostrar 2>"Fichero"
Después de dicho volcado, únicamente habría que asignar el valor que hemos introducido dentro de una variable, y tratar la información de dicha variable dentro de un case que contemplaría cada opción que hemos incluido en el menú... Eso significaría que si por ejemplo el menú tuviese tres opciones, el case tendría el mismo número de elementos... Un script bastante completo que agruparía todo esto sería este:
Las ventajas que ofrece este menú son obvias, es por ello que es una de las opciones más usadas por aquellos que optan por presentar sus scripts en TUIs para los usuarios.
Para llamar a un menú se requiere del uso del parámetro --menu; pero es necesario incluir una serie de características que harán posible la aparición de éste... Para empezar, en el establecimiento del tamaño de la interfaz, hemos de especificar el número exacto de elementos que aparecerán en el menú; eso significaría que dicha sección constaría ahora de tres parámetros, entre los cuales el número de elementos del menú sería el último de éstos tres.
Después de establecer dicho tamaño habría que imponer los elementos del menú con la siguiente estructura:
Nombre_elemento) Texto a mostrar \
El texto a mostrar sería lo representación que aparecería en pantalla mientras que el nombre que se establece antes del caracter ) sería el nombre del elemento que seleccionaríamos en el menú... Esta línea habría que repetirla tantas veces como elementos queramos incluir; a excepción del último que variaría ligeramente, en vez de establecer el carácter \ habría que especificar el fichero en el que volcaríamos nuestro resultado.
Nombre_elemento) Texto a mostrar 2>"Fichero"
Después de dicho volcado, únicamente habría que asignar el valor que hemos introducido dentro de una variable, y tratar la información de dicha variable dentro de un case que contemplaría cada opción que hemos incluido en el menú... Eso significaría que si por ejemplo el menú tuviese tres opciones, el case tendría el mismo número de elementos... Un script bastante completo que agruparía todo esto sería este:
- #!/bin/bash
- INPUT=/tmp/input.sh
- dialog --clear \
- --title "Prueba TUI" \
- --menu "Escoge uno" 15 100 3 \
- OPCION1 "Opcion1" \
- OPCION2 "Opcion2" \
- SALIR "Salir" 2>"${INPUT}"
- SELECCIONADO=$(<"${INPUT}")
- case ${SELECCIONADO} in
- OPCION1) echo "Opcion1";;
- OPCION2) echo "Opcion2";;
- SALIR) exit 0;;
- esac
- rm ${INPUT}
Mostrando los resultados de cada script
Probablemente os preguntéis por qué no he compartido el resultado de cada script al final de cada sección; esto se debe a que al ser muchas interfaces con distintos resultados, he preferido agruparlas dentro de un mismo espacio con el fin de no alargar este post innecesariamente; es por ello que os incluyo a continuación un slideshare con todos y cada uno de los resultados de cada script válido escrito hasta ahora:
Saludos.
Buenas! Por lo que tengo entendido xdialog está desatendido y su última actualización data del 2006 (http://xdialog.free.fr/doc/changelog.html). Todos los sistemas actuales (lo he comprobado en Ubuntu 10.04 en adelante, en Debian y en CentOS 6.6) ya no disponen del paquete instalado por defecto y hace falta instalarlo, lo que le quita facilidad de uso. La alternativa que viene instalado ya por defecto y en reemplazo de xdialog en los sistemas que anteriormente he mencionado es Whiptail (utilizado por ejemplo en la instalación de Postfix).
ResponderEliminarEstaría bien un artículo sobre Whiptail :-)
Por otro lado no se te puede quitar el mérito del pedazo de artículo que has elaborado. Había mirado en otras ocasiones algo sobre esto pero me guardo para favoritos lo tuyo ya que muestras todo, explicas todo y de una manera muy concisa e instructiva, con ejemplos geniales para ver realmente cómo funciona.
Un saludo y con los artículos que haces ya tienes un lector abonado a tu feed! ;-)
¡Muchas gracias por tus palabras!
EliminarCon respecto a Whiptail, lo cierto es que nunca lo he usado pero lo tendré en cuenta y revisaré esta utilidad... A primera vista he observado que ambas utilidades son muy parecidas entre sí a nivel de sintaxis, pero le echaré un vistazo a fondo para comprobar que efectivamente así es ya que seguramente incorpore novedades que dialog no posea.
Saludos cordiales.
excelente informacion gracias
ResponderEliminarMuchas gracias a ti por pasarte por este blog.
EliminarSaludos