Cómo crear un script de bash con menú gráfico e interfaces interactivas

Última actualización: 17/12/2025
Autor: Isaac
  • Un script bash puede enriquecerse con menús en texto usando estructuras como read, case y select para ofrecer varias tareas en una sola herramienta.
  • Herramientas de consola como dialog y whiptail permiten crear menús, listas y barras de progreso “gráficas” dentro del terminal de forma ligera.
  • En entornos de escritorio, Zenity proporciona cuadros de diálogo GTK que dan una interfaz gráfica completa a los scripts sin dejar de usar bash.
  • Dominar parámetros, arrays, redirecciones, funciones y códigos de salida es clave para construir scripts con interfaces robustas y fáciles de usar.

Crear script de bash con menú gráfico

Si trabajas a diario con GNU/Linux es cuestión de tiempo que termines creando scripts en bash para automatizar tareas: copias de seguridad, limpieza de temporales, monitorización, instaladores caseros… Todo esto está muy bien, pero en cuanto el script empieza a crecer llega la misma duda: ¿cómo hago que sea más cómodo de usar, con un menú gráfico o interactivo y sin tener que recordar cada parámetro?

La buena noticia es que no necesitas ser desarrollador GTK o Qt para lograrlo. Con unas cuantas herramientas muy ligeras como dialog, whiptail o zenity, puedes montar menús, cuadros de diálogo, listas y barras de progreso, tanto en terminal como en entorno gráfico, sin salir del ecosistema bash. Y si te apetece complicarte un poco más, también puedes integrar bash con interfaces más potentes o incluso con bots de Telegram.

Qué es un script bash y por qué tiene sentido ponerle un menú

Script bash con menú interactivo

Un script de bash no es más que un fichero de texto con una lista de instrucciones que la shell va ejecutando de forma secuencial. Con unas pocas líneas puedes automatizar desde tareas sencillas (ejecutar siempre la misma serie de comandos) hasta auténticas utilidades de administración con funciones, bucles y estructuras de control.

Para que Linux lo reconozca como ejecutable de bash tienes que cumplir tres detalles básicos: añadir en la primera línea el shebang correspondiente, darle permisos de ejecución y, opcionalmente, ponerle una extensión reconocible (suele usarse .sh por pura comodidad, aunque no es obligatorio).

La cabecera típica de un script sería algo como #!/bin/bash, y si quieres depurar línea a línea puedes activar el modo traza con #!/bin/bash -x. Después solo tienes que hacer un chmod +x nombre_del_script o chmod 755 nombre_del_script y ya lo podrás ejecutar con ./nombre_del_script.

Un script sencillo de “hola mundo” podría ser:

#!/bin/bash
echo "Hola. Soy tu primer script Bash"

Desde aquí puedes ir añadiendo variables, condicionales, bucles y funciones para convertirlo en una pequeña herramienta. El problema es que, a medida que crece, pedir al usuario que lo maneje solo con parámetros o recordando números en la terminal se vuelve bastante incómodo.

De ahí que tenga tanto sentido añadir un menú interactivo o gráfico por encima: la lógica sigue en bash, pero la experiencia de uso mejora muchísimo, sobre todo para personas con menos soltura en la consola.

Conceptos de bash que vas a necesitar antes de meter menús

Conceptos básicos de bash para menús

Para que tus menús (sean en texto o gráficos) no se vuelvan un caos es imprescindible controlar algunos fundamentos de bash: parámetros, arrays, estructuras de control, operadores, redirecciones y funciones. No hace falta ser un gurú, pero sí tener soltura.

En cuanto a parámetros, bash proporciona una serie de variables especiales muy útiles: $# indica cuántos argumentos se han pasado al script, $0 contiene el nombre del propio script, $1…$9 son los primeros nueve parámetros, y con ${N} puedes acceder a cualquier posición. Para manejarlos en bloque tienes $* y $@, y con $$ obtienes el PID del script. El código de salida del último comando se guarda en $?.

Un comando clave en scripts que aceptan muchos argumentos es shift. Sirve para ir desplazando los parámetros una posición hacia atrás, de forma que puedas procesar opciones como --nombre o --apellido en cualquier orden sin volverte loco. Es lo típico en scripts que aceptan banderas estilo GNU y que quieres que el usuario pueda introducir en el orden que le dé la gana.

Las variables en bash no están tipadas: pueden almacenar texto, números, arrays, lo que quieras. Puedes asignar valores directos tipo VAR=texto o construirlas a partir de la salida de un comando usando VAR=$(comando) o la forma antigua con comillas invertidas. Ojo con las comillas simples y dobles: con comillas simples no se expanden variables, mientras que con comillas dobles sí.

Otro pilar son los arrays. Puedes declararlos con ARRAY=(a b c), asignar elementos individuales con ARRAY[0]=valor y acceder con ${ARRAY[n]}. Además, dispones de utilidades como ${ARRAY[@]} para todos los elementos, ${#ARRAY[@]} para saber cuántos hay o ${!ARRAY[@]} para obtener los índices activos, algo muy útil cuando el array tiene “huecos”.

  Chromebooks sin tecla Caps Lock: cómo recuperar el bloqueo de mayúsculas y dominar el teclado

Por último, las estructuras de control clásicas (if, case, for, while, until, select) te permiten tomar decisiones y construir menús básicos sin depender todavía de herramientas externas. El combo select + case es especialmente interesante para montar menús de consola muy rápidos.

Construir menús de bash en la terminal (sin interfaz gráfica)

Menú de bash en terminal

Antes de dar el salto a un menú gráfico completo, merece la pena dominar los menús interactivos en modo texto usando exclusivamente bash. Son ligeros, funcionan por SSH sin ningún drama y, bien hechos, son muy agradables de usar.

La forma más directa consiste en mostrar una lista de opciones con echo, leer la elección con read y luego emplear un case para ejecutar la acción correspondiente. Es la típica plantilla para scripts de mantenimiento o copias de seguridad donde tienes varias tareas predefinidas.

Imagina que tienes un script que realiza cuatro tipos de backup con 7z (tmp, Dropbox, Documentos y Android Studio). En lugar de ejecutar todo del tirón, puedes montar un menú sencillo para elegir qué copia quieres hacer:

#!/bin/bash
FECHA=$(date +"%Y-%m-%d")

echo "[ Seleccione opción de Backup ]"
echo "1: tmp"
echo "2: dropbox"
echo "3: documents"
echo "4: android"

read n
case $n in
  1) 7z a -t7z /media/usuario/hddexterno/${FECHA}-tmp.7z \
         /home/usuario/tmp -mx9 -pContraseña ;;
  2) 7z a -t7z /media/usuario/hddexterno/${FECHA}-dropbox.7z \
         /home/usuario/Dropbox -mx9 -pContraseña ;;
  3) 7z a -t7z /media/usuario/hddexterno/${FECHA}-documents.7z \
         /home/usuario/Documentos -mx9 -pContraseña ;;
  4) 7z a -t7z /media/usuario/hddexterno/${FECHA}-android.7z \
         /home/usuario/AndroidStudio -mx9 -pContraseña ;;
  *) echo "Opción incorrecta" ;;
esac

Con esto ya consigues que el script deje de ser una “caja negra” y pase a tener una interfaz de texto clara. Si quieres algo más pulido, bash ofrece la construcción select, que genera un listado numerado de opciones y gestiona por ti la lectura de la selección.

Un ejemplo algo más avanzado usando select podría ser:

#!/bin/bash
FECHA=$(date +"%Y-%m-%d")
PS3="[ Seleccione opción de Backup ] "
options=("tmp" "dropbox" "documents" "android" "Salir")

select opt in "${options[@]}"; do
  case $opt in
    tmp)
      7z a -t7z /media/usuario/hddexterno/${FECHA}-tmp.7z \
         /home/usuario/tmp -mx9 -pContraseña ;;
    dropbox)
      7z a -t7z /media/usuario/hddexterno/${FECHA}-dropbox.7z \
         /home/usuario/Dropbox -mx9 -pContraseña ;;
    documents)
      7z a -t7z /media/usuario/hddexterno/${FECHA}-documents.7z \
         /home/usuario/Documentos -mx9 -pContraseña ;;
    android)
      7z a -t7z /media/usuario/hddexterno/${FECHA}-android.7z \
         /home/usuario/AndroidStudio -mx9 -pContraseña ;;
    Salir)
      break ;;
    *)
      echo "Opción incorrecta" ;;
  esac
done

Como ves, con muy poco código tienes un menú repetible donde el usuario puede elegir varias opciones seguidas hasta que marca “Salir”. Este patrón se puede reutilizar en prácticamente cualquier script en el que quieras ofrecer varias operaciones agrupadas.

Dialog y whiptail: menús “gráficos” en modo texto

Cuando quieres ir un poco más allá en la terminal, entran en juego herramientas como dialog y whiptail. Siguen funcionando sobre consola de texto, pero permiten construir ventanas, menús con cursores, checklists, barras de progreso y más, todo controlado desde el script mediante argumentos.

dialog es muy conocido en el mundo GNU/Linux: muchos instaladores, como el de Slackware, están montados como un conjunto de scripts bash apoyados en dialog. Con él puedes crear desde simples mensajes de información hasta formularios complejos. Su sintaxis gira siempre alrededor de un comando tipo dialog --opcion ... que devuelve el resultado por stdout o mediante el código de salida.

Un ejemplo clásico de menú con cursores en dialog sería:

respuesta=$(dialog --title "Ejemplo de menú" \
                  --stdout \
                  --menu "Opciones" 12 40 4 \
                  1 "Opción 1" \
                  2 "Opción 2" \
                  3 "Opción 3" \
                  4 "Opción 4")

echo "Has elegido: $respuesta"

La opción –stdout hace que la elección del usuario se envíe a la salida estándar, de manera que puedas guardarla directamente en una variable con $(...). Las cifras 12 40 4 indican alto, ancho y número máximo de elementos visibles en el menú.

Además del menú simple, dialog soporta msgbox (mensajes informativos), yesno (confirmaciones), inputbox (entradas de texto), checklist (listas con casillas), radiolist (selección única) y gauge (barras de progreso), entre otros muchos tipos. Conviene echar un ojo al man o a la documentación oficial para ver el abanico completo.

whiptail es una alternativa más ligera basada en la librería newt. Ofrece un conjunto de diálogos muy parecido a dialog, con una sintaxis casi calcada, pero a veces es preferible porque consume menos recursos o está ya disponible en entornos minimalistas.

Un menú construido con whiptail tendría un aspecto así:

#!/bin/bash
opcion=$(whiptail --title "Menú Principal" \
                  --menu "Seleccione una opción:" 15 50 4 \
                  "1" "Ver información del sistema" \
                  "2" "Mostrar uso del disco" \
                  "3" "Configurar red" \
                  "4" "Salir" 3>&1 1>&2 2>&3)

clear
case $opcion in
  1) echo "Mostrando información del sistema..." ;;
  2) echo "Mostrando uso del disco..." ;;
  3) echo "Configurando la red..." ;;
  4) echo "Saliendo..." ; exit 0 ;;
  *) echo "Opción no válida." ;;
esac

El truco de 3>&1 1>&2 2>&3 se usa mucho con whiptail para redirigir la salida de la selección al descriptor adecuado y poder capturarla con $(...). Aunque parezca un galimatías, en la práctica lo reutilizas siempre igual.

  Google presenta Live Update Orchestrator para actualizar el kernel de Linux sin interrupciones

Dialog y whiptail en detalle: mensajes, listas y barras de progreso

Una vez tienes instalado dialog o whiptail (en Debian/Ubuntu con un sudo apt-get install dialog o sudo apt-get install whiptail, y en RHEL/CentOS vía yum con los paquetes correspondientes), puedes empezar a montar interfaces cada vez más amigables.

Por ejemplo, un mensaje simple con dialog es tan sencillo como:

#!/bin/bash
dialog --title "Mensaje" \
       --msgbox "Hola, este es un cuadro de mensaje simple." 6 50

Si lo que quieres es un menú principal con varias acciones, tienes una plantilla típica así:

#!/bin/bash
opcion=$(dialog --title "Menú Principal" \
               --menu "Selecciona una opción:" 15 50 4 \
               1 "Opción 1" \
               2 "Opción 2" \
               3 "Opción 3" \
               4 "Salir" 3>&1 1>&2 2>&3)

clear
echo "Has seleccionado la opción: $opcion"

Las listas de selección múltiple se construyen con --checklist. Un ejemplo típico sería un instalador que deja elegir qué paquetes instalar:

#!/bin/bash
opciones=$(dialog --title "Selección de Paquetes" \
                 --checklist "Selecciona los paquetes que deseas instalar:" \
                 15 50 5 \
                 "Apache" off \
                 "MySQL"  off \
                 "PHP"    off \
                 "Python" off \
                 "Java"   off 3>&1 1>&2 2>&3)

clear
echo "Paquetes seleccionados: $opciones"

La palabra off indica que la casilla está desmarcada al inicio, mientras que en whiptail se suele usar ON/OFF en mayúsculas. El formato del resultado suele ser una lista de elementos seleccionados separada por espacios o comillas, según configuración.

Para mostrar el progreso de una tarea larga, dialog dispone de –gauge. La idea es enviarle valores numéricos (0-100) por la entrada estándar mientras tu script hace el trabajo real:

#!/bin/bash
{ 
  for ((i = 0; i <= 100; i+=10)); do
    sleep 1
    echo $i
  done
} | dialog --title "Progreso" --gauge "Instalando..." 10 70 0

whiptail ofrece algo casi idéntico usando su propia opción –gauge. En ambos casos la lógica es la misma: un bucle que actualiza el porcentaje y lo alimenta por una tubería a la barra de progreso.

Interfaces gráficas reales con Zenity (GTK) para tus scripts

Si te mueves en un entorno con servidor gráfico (Gnome, KDE, etc.) y quieres ir un paso más allá, puedes utilizar Zenity para construir cuadros de diálogo GTK directamente desde bash. Zenity está pensado justo para eso: dar una “cara gráfica” a tus scripts sin necesidad de programar una aplicación completa.

La instalación en Debian/Ubuntu es tan simple como:

sudo apt-get install zenity

En sistemas basados en Red Hat la idea es la misma, normalmente con sudo yum install zenity o el paquete equivalente. Una vez instalado, lo llamas desde la terminal o desde tu script con comandos del estilo zenity --info ....

Zenity cuenta con varios tipos de diálogos: info, warning, error, question, file-selection, forms, listas, barras de progreso y más. Por ejemplo, un simple mensaje informativo:

zenity --info --text="Información"

O una advertencia o error:

zenity --warning --text="Advertencia"
zenity --error   --text="Error"

La gracia está en guardar la ruta en una variable capturando la salida de Zenity dentro del script. Si quieres que el usuario seleccione un fichero y guardar la ruta en una variable, podrías hacer algo así:

file=$(zenity --file-selection)

A partir de ese momento puedes trabajar con $file igual que con cualquier ruta introducida por teclado. Es una forma estupenda de pedir parámetros sin obligar a escribir rutas interminables en la terminal.

Ejemplos avanzados: notificaciones, barras en consola y automatización web

Además de los menús, bash se lleva muy bien con toda una ristra de pequeñas utilidades que te permiten montar soluciones bastante elaboradas sin salir del ecosistema de consola. Algunos ejemplos prácticos que encajan muy bien con interfaces basadas en menús son los siguientes.

Para interacción web puedes usar navegadores de texto como lynx o descargadores como wget. Un caso real: comprobar si hay mensajes nuevos en los foros de un proyecto alojado en SourceForge. El script descarga la página con lynx, extrae las líneas que contienen “Last Action”, las guarda en un archivo y las compara con la última ejecución usando diff. Si hay cambios, sabes que hay algo nuevo que mirar.

Otro ejemplo típico es automatizar compilaciones o tareas de desarrollo. Por ejemplo, compilar pequeños programas OpenGL/GLUT para Windows bajo Linux usando MinGW y wine con una sola línea de script, generando automáticamente el nombre del ejecutable a partir del nombre del .cpp con ayuda de sed y sustituciones de cadenas.

Las notificaciones de escritorio también son un gran complemento a scripts que se ejecutan “en segundo plano” mientras haces otra cosa. La herramienta notify-send (del paquete libnotify-bin en muchas distros) te deja lanzar globos de aviso en la barra de tareas, ideal para avisar de que una copia de un DVD con dd ha terminado, por ejemplo:

notify-send -t 4000 "Terminó!"

Si prefieres interfaces completamente de texto pero vistosas, puedes combinar múltiples utilidades como df, ls, bc, ps, xrandr o tput para mostrar barras de progreso en consola, animaciones (como una nevada con caracteres) o incluso scripts que calculen la resolución de la pantalla y la usen para grabar screencasts con ffmpeg.

  Cómo elegir una cinta de correr para hacer ejercicio mientras trabajas con el ordenador

Bash avanzado: redirecciones, tuberías y control de errores

Detrás de cualquier menú decente suele haber un buen manejo de entrada y salida estándar. En Linux todo pasa por tres descriptores: stdin (0), stdout (1) y stderr (2). Según redirijas cada uno podrás guardar resultados en ficheros, ocultar errores, mezclarlos o encadenar comandos con tuberías.

La redirección básica > envía stdout a un fichero (sobrescribiéndolo), mientras que >> añade al final. Si quieres separar resultado y errores, tienes sintaxis como 1>fichero y 2>errores. También puedes mandar los errores al mismo sitio que la salida con 2>&1 o viceversa.

Un patrón muy usado es generar un listado y guardar solo el resultado silenciosamente, ignorando errores redirigiéndolos a /dev/null. Por ejemplo:

ls -1 /tmp/Carpeta1 1>/tmp/lista_ficheros.txt 2>/dev/null

Las tuberías (|) son la otra pata importante. Permiten pasar la salida de un comando a la entrada del siguiente, formando cadenas muy potentes. Un clásico: ordenar la lista de archivos con sort o calcular la resolución de pantalla con xrandr | grep | awk encadenados. Este mismo mecanismo es el que se usa para alimentar barras de progreso de dialog/whiptail o formularios de Zenity.

En cuanto al control de errores, todos los comandos devuelven un código de salida accesible con $?. Los scripts bien hechos suelen almacenar ese valor en una variable y devolverlo al final con exit, permitiendo que el sistema o otros scripts sepan si todo ha ido bien (código 0) o ha fallado algo (cualquier valor distinto de 0).

Dentro de funciones, el equivalente a exit es return, que también acepta solo números. Si quieres devolver otros tipos de datos desde una función (texto, arrays), lo habitual es usar variables globales o escribir en stdout y capturar la salida con $(funcion).

Dar un icono de lanzador gráfico a un script bash en Debian

Otro caso muy habitual cuando empiezas a mezclar bash con interfaz gráfica es querer lanzar un script desde un icono en Gnome o desde el gestor de archivos, sin tener que abrir un terminal y escribir el comando a mano.

Por ejemplo, imagina que tienes un script myscripts/doom2.sh que ejecuta chocolate-doom -iwad WAD/DOOM2.WAD. Lo has probado en la terminal con bash myscripts/doom2.sh y funciona perfecto, pero quieres arrancarlo con doble clic o desde un lanzador del escritorio en Debian con Gnome.

En entornos basados en Gnome normalmente basta con crear un archivo .desktop en ~/.local/share/applications (para el usuario actual) o en /usr/share/applications (a nivel de sistema), con un contenido parecido a este:

[Desktop Entry]
Type=Application
Name=Doom 2 (Chocolate Doom)
Exec=/ruta/completa/myscripts/doom2.sh
Icon=application-x-executable
Terminal=false
Categories=Game;

Después de darle permisos y actualizar el índice de aplicaciones, podrás encontrarlo en el menú o anclarlo al dock. Si prefieres lanzarlo con doble clic desde el gestor de archivos, asegúrate de que el script tiene permiso de ejecución y que el entorno está configurado para “ejecutar” en lugar de “abrir” los scripts de texto.

Combinando esto con herramientas como Zenity puedes crear pequeñas “aplicaciones” caseras de escritorio que no son más que scripts de bash con un par de cuadros de diálogo por encima.

Con todo lo visto, ya tienes sobre la mesa las piezas clave para pasar de scripts de consola planos a utilidades con menús gráficos o pseudo-gráficos que cualquiera puede manejar sin sustos: menús sencillos con echo/read, menús enriquecidos con dialog y whiptail, cuadros GTK con Zenity, notificaciones, barras de progreso y una buena base de bash: parámetros, arrays, redirecciones, funciones y control de errores. A partir de aquí toca experimentar, adaptar los ejemplos a tus necesidades y pulir la experiencia de uso de tus scripts hasta que parezcan pequeñas aplicaciones hechas y derechas.