Ejemplos de cómo crear scripts con menús de opciones en Bash

Última actualización: 28/01/2026
Autor: Isaac
  • Los menús en Bash se construyen combinando estructuras como read, case y select, permitiendo elegir acciones sin recordar comandos complejos.
  • Organizar el código en funciones independientes (crear, validar, eliminar, listar) facilita mantener y ampliar scripts con menús interactivos.
  • Herramientas como dialog añaden interfaces de texto avanzadas con cajas y navegación por cursores, ideales para menús más visuales en terminal.
  • La integración con cron y buenas prácticas de depuración convierten estos menús en soluciones robustas para automatizar y controlar tareas en Linux.

Menús en scripts Bash

Si trabajas con GNU/Linux a diario, tarde o temprano te planteas cómo automatizar tareas repetitivas sin perder control sobre lo que se ejecuta. Ahí es donde entran en juego los scripts en Bash con menús de opciones: pequeños programas en la terminal que te preguntan qué quieres hacer y actúan en consecuencia.

Lejos de ser algo reservado a gurús de la consola, crear estos menús es bastante asequible. Con unas pocas construcciones básicas de Bash —select, case, read, bucles y alguna utilidad extra como dialog para menús gráficos— puedes montar desde un sencillo selector numérico hasta interfaces de texto interactivas con flechas de cursor, cuadros emergentes y validaciones de datos.

Conceptos básicos: qué es un script Bash con menú de opciones

Un script Bash no es más que un archivo de texto con comandos que el intérprete ejecuta uno detrás de otro. La gracia de añadir un menú es que en lugar de lanzar siempre la misma secuencia, el usuario puede elegir qué acción ejecutar en cada momento, escribiendo un número, moviéndose con las flechas o seleccionando una entrada concreta.

En este contexto, un menú de opciones en Bash puede adoptar varias formas: desde el clásico listado numerado con read + case, hasta cajas de diálogo a pantalla completa con la utilidad dialog, pasando por los menús automáticos que proporciona la palabra clave select integrada en el propio Bash.

Este tipo de scripts se usan, por ejemplo, para gestionar usuarios de una aplicación sencilla, lanzar copias de seguridad de diferentes carpetas, mostrar utilidades del sistema (procesos, calendario, hostname) o encapsular varias tareas de administración bajo una interfaz unificada y amigable en la consola.

Más allá de la comodidad, estos menús son útiles porque permiten a personas con menos experiencia en la línea de comandos ejecutar acciones complejas sin recordar rutas, opciones ni parámetros, reduciendo errores y ahorrando tiempo.

Menús simples con read y case: el enfoque más directo

La forma más directa de construir un menú en Bash consiste en mostrar una lista numerada de opciones, leer una tecla o un número con read y, en función de lo que haya introducido el usuario, ejecutar un bloque de código u otro mediante una estructura case ... esac.

Imagina que tienes un script de copias de seguridad que comprime cuatro carpetas distintas con 7z. Sin menú, el script simplemente lanzaría todas las órdenes de una tacada, generando cuatro archivos de backup cada vez que lo ejecutas, aunque solo quieras actualizar uno de ellos.

La solución es agrupar esos comandos en un único script y añadirle un pequeño menú para elegir qué backup lanzar. El patrón básico que se suele emplear es:

1) Definir las variables necesarias, como la fecha actual para nombrar los ficheros de salida del backup.

2) Mostrar las opciones con varios echo, por ejemplo, una línea de cabecera y cuatro líneas para cada tarea: carpeta temporal, Dropbox, Documentos y Android Studio.

3) Leer del teclado la opción elegida, usando read para capturar un solo carácter o un número, que luego se guardará en una variable (por ejemplo, n).

4) Usar una sentencia case $n in ... esac para decidir qué comando ejecutar según el valor: cada rama del case contiene la orden 7z correspondiente a la carpeta elegida, y además se define un caso comodín *) para capturar opciones inválidas y avisar al usuario.

Ejemplo práctico: menú de copias de seguridad en Bash

Un caso muy típico en entornos domésticos o de pequeña oficina es tener un script que lance backups manuales a un disco externo. En lugar de mantener cuatro scripts separados o de ejecutar siempre todos los respaldos, podemos centralizar la lógica en un único menú interactivo.

El esqueleto del script de ejemplo suele comenzar con la línea shebang #!/bin/bash, seguida de la configuración de la fecha:

FECHA se obtiene con date +"%Y-%m-%d", de modo que todos los ficheros generados queden identificados por día, algo muy práctico para ir acumulando copias historizadas.

Después se presenta al usuario un bloque como este (escrito aquí de forma explicativa): una línea con el texto y cuatro líneas siguientes numeradas de 1 a 4, cada una describiendo una carpeta: tmp, dropbox, documents y android. Esas descripciones sirven para que sea evidente qué se va a respaldar en cada caso.

Con un simple read n se captura la elección. A partir de ahí, un case $n in permite asociar cada número con el comando concreto de 7z que comprime la carpeta adecuada en la unidad externa. El uso de compresión sólida con -mx9 y una contraseña con -pContraseña añade seguridad y eficiencia, y el esquema de nombres con fecha y sufijo de carpeta (por ejemplo, $FECHA-tmp.7z) ayuda a localizar fácilmente cada archivo.

La gran ventaja de este planteamiento es que, sin duplicar lógica ni mantener múltiples ficheros, conseguimos que el usuario pueda ejecutar solo la tarea que le interesa. Y si un día queremos añadir una nueva carpeta de backup, basta con incorporar una nueva opción en el menú y su rama en el case.

Menús avanzados con select: aprovechando características propias de Bash

Bash incluye una palabra clave muy cómoda para generar menús numerados de manera automática: select. Con ella no hace falta escribir a mano todos los echo para mostrar las opciones ni gestionar los números de índice; el propio intérprete se encarga de imprimir el menú y capturar la elección.

La estructura típica de un menú con select es algo así: se define un array con las distintas alternativas, se ajusta una variable especial PS3 con el texto del prompt (por ejemplo, «») y luego se abre un bucle select opt in "1" "2" "3" "Quit"; do ... done.

Dentro del do ... done se vuelve a utilizar un case $opt in para enlazar cada valor del array con una acción concreta. En el ejemplo de las copias de seguridad, las entradas «1», «2» y «3» se corresponden con cada conjunto de carpetas o backups que queremos lanzar, y una opción adicional como «Quit» permite salir del menú con un simple break.

La gran diferencia respecto al enfoque con read es que select imprime automáticamente la lista de alternativas numeradas, y el usuario solo tiene que teclear el número de la opción. Además, Bash gestiona la variable REPLY con el valor introducido, lo que facilita la validación en casos más complejos.

  El archivo Desktop.ini de Windows 10 y sus métodos de ocultación

En menús algo más elaborados, select resulta especialmente útil porque reduce la cantidad de código repetitivo y hace que el script sea más legible y mantenible, sobre todo cuando la lista de opciones crece con el tiempo.

Crear menús estructurados: ejemplo de gestión de usuarios con funciones

Cuando el menú controla tareas más delicadas, como la gestión de cuentas de usuario en una aplicación interna, conviene organizar el script en funciones bien separadas. En uno de los ejemplos clásicos se definen al menos cuatro funciones: solicitar_usuario(), validar_usuario(), crear_usuario() y eliminar_usuario(), además del bucle principal que muestra el menú.

La función solicitar_usuario() se encarga de pedir interactivamente al usuario un nombre de cuenta y una contraseña, con doble confirmación. Internamente, recoge un mensaje opcional pasado como parámetro para mostrar un texto distinto dependiendo del contexto (primera alta, repetición por usuario duplicado, corrección de contraseña, etc.).

En esa misma función se usa read -p para solicitar el nombre de usuario y read -sp para la contraseña, ocultando la entrada en la terminal. Después, se pide repetir la contraseña en otra variable, y se muestra un salto de línea con echo "" para no dejar el prompt pegado al texto anterior.

La segunda pieza clave es validar_usuario(), que verifica si el nombre proporcionado ya existe. Normalmente se apoya en un archivo de texto plano (por ejemplo, accesos/cuentas) donde se lista una cuenta por línea. Mediante grep se busca el identificador, y se establece una variable de control, como validacion, a 0 o 1 dependiendo de si ha habido coincidencia.

Esta función devuelve su resultado mediante return, lo que permite utilizarla en condiciones del tipo while validar_usuario "$usuario"; do ... done. De este modo, crear_usuario() puede invocar a solicitar_usuario tantas veces como sea necesario hasta que el nombre sea nuevo y las contraseñas coincidan.

Dentro de crear_usuario(), tras la recogida de datos, se establecen dos bucles: uno que se repite mientras la cuenta exista ya en el fichero, mostrando un mensaje como «El usuario elegido ya existe…», y otro que se repite si la contraseña no coincide con la confirmación, forzando a reintroducir ambos campos. Una vez superadas las validaciones, la función añade el nombre de usuario a accesos/cuentas con echo "$usuario" >> accesos/cuentas y confirma por pantalla con un texto tipo «usuario creado».

La función eliminar_usuario() sigue una filosofía parecida, pero enfocada a la baja de cuentas. Primero solicita el nombre a borrar con read -p, y a continuación usa un bucle junto a validar_usuario para asegurarse de que la cuenta existe realmente. Si el usuario no se encuentra, se muestra un aviso de «Usuario no encontrado» y se abandona la función con return 1.

Cuando la cuenta es válida, la eliminación se realiza con un truco habitual: se ejecuta grep -Ev "$usuario" accesos/cuentas > accesos/cuentas_tmp para generar un archivo temporal sin la línea correspondiente, y luego se renombra con mv accesos/cuentas_tmp accesos/cuentas. Finalmente se notifica la operación con un mensaje como «usuario eliminado».

Bucle principal del script: construir el menú de gestión

Una vez definidas las funciones auxiliares, se monta un bucle infinito que actúa como menú principal. Suele escribirse como while true; do ... done, de modo que el script no termine tras ejecutar una sola acción, sino que regrese de nuevo al listado de opciones hasta que el usuario elija salir.

Dentro del bucle, una llamada a clear limpia la pantalla para tener un entorno más ordenado. A continuación, se imprimen con echo el título del menú (por ejemplo, «Gestión de usuarios») y las distintas alternativas numeradas: crear usuario, eliminar usuario, listar usuarios y salir.

La captura de la opción se hace con un read -sn1 opcion, que lee un solo carácter sin eco (o con eco mínimo) y lo almacena en la variable correspondiente. Esto permite que el usuario solo tenga que pulsar una tecla del 1 al 4, sin necesidad de confirmar con Enter en algunos casos, según cómo se combine.

Seguidamente se añade un echo "" para bajar a la siguiente línea y se entra en el bloque case que atiende a cada opción. La rama «1» invoca a crear_usuario, la «2» llama a eliminar_usuario, la «3» usa cat accesos/cuentas para mostrar todos los usuarios y después un read -n1 -p "" o similar para hacer una pausa antes de volver al menú, y la «4» ejecuta exit 0 para abandonar el script.

Tras el esac, se suele incluir una pequeña pausa adicional, como read -n1 -p "Presione cualquier tecla", que evita que la pantalla se refresque demasiado rápido si la acción acaba sin interacción visible (por ejemplo, tras crear un usuario). Así el flujo resulta más cómodo e intuitivo para quien está al teclado.

Este patrón de bucle + menú + case es extensible a casi cualquier tarea: siempre que tengas un conjunto de acciones bien delimitadas que quieras presentar al usuario, puedes empaquetarlas en funciones y enlazarlas desde una estructura similar.

Usar select para construir menús de backup más elegantes

Retomando el ejemplo de las copias de seguridad, una variante más pulida del menú consiste en aprovechar la palabra clave select en lugar de combinar manualmente echo y read. En este caso, lo primero es fijar la variable PS3 con el mensaje que aparecerá como prompt, por ejemplo: PS3="".

Después se define un array, como options=("1" "2" "3" "Quit"), y se escribe select opt in "${options}"; do ... done. Bash mostrará automáticamente un menú numerado con esas cuatro entradas, y el usuario elegirá introduciendo el número que corresponda a la que quiera.

  ¿Cómo puedo restablecer, reiniciar o restaurar mi teléfono LG bloqueado?

Dentro del cuerpo del select se implementa un case $opt in para distinguir entre las opciones. En la RAMA «1» se ejecuta el comando 7z para respaldas la carpeta temporal, en «2» el backup de Dropbox, en «3» la carpeta de Documentos y así sucesivamente. Al llegar a la opción «Quit», se usa break para terminar el select y, en consecuencia, dejar de mostrar el menú.

Al igual que antes, se incluye una rama *) para las entradas no previstas, desde la que se imprime algo tipo «Opción incorrecta» si el usuario introduce un valor que no encaje en la lista. Este enfoque facilita agrupar muchas tareas de mantenimiento en un único script, sin necesidad de recordar nombres de ficheros individuales.

Aunque conceptualmente no hace nada que no pueda hacerse con read + case, select reduce ruido visual en el código y gestiona automáticamente algunos detalles, por lo que es una opción interesante cuando se quiere mantener el script claro y conciso.

Menús interactivos con dialog: interfaces tipo “caja” en la terminal

Si quieres ir un paso más allá, puedes recurrir a utilidades de interfaz de texto como dialog, que permiten mostrar menús con bordes, títulos, botones y cuadros en modo texto, al estilo de aplicaciones antiguas tipo ncurses. Estas herramientas son perfectas cuando buscas una experiencia más visual y navegable con el teclado.

Con dialog se pueden crear menús navegables con flechas de cursor y atajos de teclado en Linux. Un ejemplo típico es un script que presenta un cuadro con un título como «Utilidades» o «MENU PRINCIPAL», una lista de opciones («Procesos», «Calendario», «Host», «Exit») y un botón de ayuda adicional. El usuario puede moverse con las teclas de dirección, usar las letras resaltadas o simplemente pulsar números del 1 al 9 para elegir.

La mecánica general en este tipo de script es la siguiente: se define un archivo temporal para almacenar la opción elegida (por ejemplo, INPUT=/tmp/menu.sh.$$) y otro para capturar la salida de los comandos que se mostrarán en cuadros de texto (OUTPUT=/tmp/output.sh.$$), usando la variable $$ para diferenciar cada proceso.

Mediante la instrucción trap se configura que, al recibir ciertas señales como SIGHUP, SIGINT o SIGTERM, el script ejecute una rutina de limpieza que borre esos ficheros temporales y salga ordenadamente. De esta forma se evitan residuos en el sistema si el usuario interrumpe el script con Control+C o si el proceso se termina abruptamente.

Una función como muestraSalida() encapsula la lógica para mostrar una caja de mensaje con dialog. Recoge parámetros como la altura, el ancho y el título de la ventana, y después invoca a dialog --backtitle "Utilidades" --title "${titulo}" --clear --msgbox "$(cat $OUTPUT)" altura ancho (o variante equivalente) para presentar el contenido de OUTPUT en un cuadro limpio.

Otras funciones específicas, como muestraHost() o muestraCalendario(), generan la información que se quiere visualizar. Por ejemplo, muestraHost() ejecuta hostname y redirige el resultado a $OUTPUT, para luego llamar a muestraSalida 6 40 "Nombre del Host". Del mismo modo, muestraCalendario() ejecuta cal y muestra un pequeño calendario mensual en una caja con título «Calendario».

En el bucle principal, dialog --menu se encarga de construir el menú de opciones propiamente dicho. Se especifican parámetros como titulo de fondo (--backtitle), título de la caja (--title), texto descriptivo de uso de las flechas y números, dimensiones (alto, ancho, número máximo de elementos visibles) y finalmente las parejas «etiqueta»-«descripción» para cada opción del menú.

La elección del usuario se almacena redirigiendo la salida estándar de dialog al archivo de entrada (2>"${INPUT}"). Luego se lee ese archivo con algo como opcionesMenu=$(<"${INPUT}") y se usa un case opcionesMenu in para desencadenar la función correspondiente: mostrar procesos, mostrar calendario, enseñar el hostname o salir del menú.

Al terminar el bucle (cuando el usuario elige «Exit»), el script elimina los archivos temporales si todavía existen, usando comprobaciones como && rm $OUTPUT y lo mismo con $INPUT. De este modo, el sistema queda limpio y preparado para futuras ejecuciones.

Menús con select más dialog: selección mediante cursores

Además del uso de dialog para cuadros de mensaje, existe una integración muy interesante: crear menús que no solo acepten números, sino que permitan mover un cursor por las distintas opciones, como si fuera un menú desplegable de aplicaciones tipo Midnight Commander.

La utilidad dialog incluye, entre otras cosas, un tipo de cuadro específico para menús: --menu. Con una llamada del estilo respuesta=$(dialog --title "Ejemplo de menu" --stdout --menu "Opciones" 12 20 5 1 "Opción 1" 2 "Opción 2" 3 "Opción 3" 4 "Opción 4"), se presenta en pantalla una caja con título y una lista numerada; el usuario se mueve con las flechas, selecciona una entrada y dialog devuelve el valor asociado (por ejemplo, «1», «2», etc.), que se guarda en la variable respuesta.

Gracias a la opción --stdout, la selección se escribe en la salida estándar, pudiéndose capturar directamente en una sustitución de comandos como la anterior. A partir de ahí, basta con usar un case "$respuesta" in para actuar según la opción elegida. Esta variante es especialmente útil en conexiones SSH en modo texto, donde no hay interfaz gráfica pero sí se desea una experiencia interactiva cómoda.

En escenarios más grandes, es habitual combinar dialog con funciones de Bash y, si se necesita, con otros lenguajes como Python para tareas más complejas, por ejemplo, construir bots de Telegram donde el menú de opciones viaja dentro del propio chat, pero la lógica de selección y tratamiento sigue un esquema muy parecido al de los menús de terminal.

  EFSDump: Qué es, para qué sirve y cómo usar a fondo esta herramienta de Sysinternals

El comando select “puro” en Bash para menús ligeros

Aunque ya hemos visto usos prácticos de select, merece la pena detenerse en la sintaxis básica del comando tal y como lo proporciona Bash, porque es una herramienta muy ligera para construir menús sencillos sin dependencias externas.

La estructura genérica es: se define un array con las opciones, y luego se escribe select opt in "${opciones}"; do ... done. Cada vez que el usuario introduce un número, Bash asigna el elemento correspondiente del array a la variable opt y el valor introducido en crudo a $REPLY.

Un menú típico podría presentar un mensaje como «Selecciona una opción:» y un conjunto de alternativas: «Opción 1», «Opción 2», «Opción 3» y «Salir». Dentro del bucle, una estructura case $opt in permite asignar a cada una de esas cadenas la acción que corresponda: mostrar un mensaje, ejecutar una función, invocar otro script, etc.

Para cerrar el menú, se establece que cuando el usuario escoja «Salir» se ejecute un break que rompe el bucle select. Además se puede usar la rama *) para interceptar entradas erróneas (por ejemplo, números fuera de rango) y mostrar un aviso como «opción inválida $REPLY».

Esta forma de menú es especialmente adecuada cuando quieres algo muy portable y sin adornos: no requiere ninguna utilidad extra más allá de Bash, funciona igual en entornos gráficos o en SSH, y es lo bastante expresiva como para cubrir la mayoría de flujos interactivos básicos.

Bases de programación en Bash que necesitas para tus menús

Para sacarle jugo a todo lo anterior conviene dominar lo fundamental de la programación en Bash. En primer lugar, el uso del shebang (#!/bin/bash o la ruta que corresponda en tu sistema) y el manejo de finales de línea (CRLF vs LF) que afectan a la portabilidad.

También es importante entender los permisos de ejecución: un script no correrá directamente hasta que le asignes el bit de ejecución con un comando como chmod u+x nombre_script.sh. A partir de ahí, puedes llamarlo con ./nombre_script.sh o mediante bash nombre_script.sh, como prefieras.

En cuanto a entrada y salida, comandos como echo imprimen cadenas de texto o el valor de variables, read permite recopilar información del usuario, y la redirección con > y >> te permite escribir o añadir contenidos a ficheros. Aprender a manejar estas redirecciones será clave si quieres que tu menú gestione archivos de configuración, listados de usuarios o logs.

Las variables en Bash no tienen tipos estrictos: pueden contener números, texto o cualquier cadena sin que el intérprete distinga demasiado. Se declaran sencillamente como nombre=valor, y se referencian con $nombre. Es recomendable seguir un estilo de nombres claro y descriptivo, empezando por letra o guion bajo, evitando espacios y palabras reservadas como if, then o else.

Por último, las estructuras de control como if ... then ... fi, bucles while y for y sentencias case son el pegamento que mantiene unido todo el flujo de los menús: decidir qué opción se ha elegido, cuándo repetir una pregunta, cómo validar un campo o en qué momento abandonar el script.

Automatizar la ejecución de scripts con cron y depurarlos cuando fallan

Una vez que tus menús de Bash funcionan de forma interactiva, quizá quieras automatizar parte de esas tareas para que se ejecuten sin intervención, por ejemplo, programar un backup nocturno con ciertas opciones por defecto. Ahí entra en escena cron, el programador de tareas clásico en sistemas tipo Unix.

La sintaxis de una entrada de cron sigue este esquema: minuto hora día mes día_semana comando. Algo como 0 0 * * * /ruta/a/script.sh lanza el script cada día a medianoche, mientras que */5 * * * * /ruta/a/script.sh lo ejecuta cada cinco minutos, y 0 6 * * 1-5 hará que se ejecute a las 6 de la mañana de lunes a viernes.

Para gestionar tus tareas programadas usas crontab -l (listar trabajos actuales) y crontab -e (editar). En esos scripts puedes reutilizar funciones y lógica de tus menús, solo que en este caso las opciones se fijan mediante argumentos de línea de comandos o variables internas, sin interacción con el usuario.

A la hora de depurar scripts de Bash, hay varias técnicas clave: activar set -x near the comienzo del script hace que Bash imprima cada comando que ejecuta precedido por un «+», con lo que puedes seguir el rastro paso a paso. Añadir set -e provoca que el script termine automáticamente si algún comando devuelve un código de salida distinto de 0.

La variable $? contiene siempre el código de salida del último comando ejecutado, por lo que puedes usarla junto con condicionales para detectar errores: if ; then echo "Hubo un error."; fi. Y, por supuesto, los mensajes de depuración con echo insertados en puntos estratégicos ayudan a ver el contenido de variables y el flujo real de ejecución.

Cuando la automatización implica cron, revisar los logs del sistema es esencial. En distribuciones como Debian o Ubuntu, muchos mensajes relacionados con cron acaban en /var/log/syslog, desde donde puedes verificar si los jobs se han lanzado, si los scripts han devuelto errores o si hay problemas de permisos.

Con todas estas piezas, desde los menús más sencillos con read y case hasta las interfaces más vistosas con dialog, pasando por el uso de select, funciones bien estructuradas, automatización con cron y técnicas básicas de depuración, tienes a tu alcance un abanico enorme de posibilidades para crear scripts en Bash con menús de opciones que hagan tu día a día en la terminal mucho más cómodo y controlado, tanto para tareas de usuario común como para pequeños paneles de administración en servidores y equipos personales.

crear script de bash con menú gráfico
Artículo relacionado:
Cómo crear un script de bash con menú gráfico e interfaces interactivas