- Un script Bash can be enriched with text menus using structures like read, case, and select to offer multiple tasks in a single tool.
- Console tools like dialog and whiptail allow you to create "graphical" menus, lists, and progress bars within the terminal lightly.
- In desktop environments, Zenity provides GTK dialog boxes that give a complete graphical interface to scripts while still using bash.
- Mastering parameters, arrays, redirects, functions, and exit codes is key to building scripts with robust and easy-to-use interfaces.

If you work with GNU/Linux daily, it's just a matter of time before you end up creating bash scripts to automate tasksBackups, temporary file cleanup, monitoring, home installers… All this is great, but as soon as the script starts to grow, the same question arises: how do I make it more user-friendly, with a graphical or interactive menu And without having to remember every parameter?
The good news is that you don't need to be a GTK or Qt developer to achieve this. With a few very lightweight tools like dialog, whiptail or zenityYou can create menus, dialog boxes, lists, and progress bars, both in the terminal and in a graphical environment, without leaving the bash ecosystem. And if you want to get a little more complex, you can also integrate bash with more powerful interfaces or even with Telegram bots.
What is a bash script and why does it make sense to add a menu to it?

Un bash script It's simply a text file containing a list of instructions that the shell executes sequentially. With just a few lines, you can automate everything from simple tasks (always running the same series of commands) to complex operations. commands) up to authentic administration utilities with functions, loops and control structures.
For Linux To recognize it as a bash executable, you have to meet three basic requirements: add the following to the first line: shebang corresponding, give it execution permissions and, optionally, give it a recognizable extension (usually .sh is used for convenience, although it is not mandatory).
A typical script header would look something like this: #! / Bin / bashAnd if you want to debug line by line, you can activate trace mode with #!/bin/bash -xThen you just have to do a chmod +x nombre_del_script o chmod 755 nombre_del_script and then you can run it with ./nombre_del_script.
A simple script "Hello world" could be:
#!/bin/bash
echo "Hola. Soy tu primer script Bash"
From here you can add variables, conditionals, loops, and functions to turn it into a small tool. The problem is that, as it grows, asking the user to manage it solely with parameters or by remembering numbers in the terminal becomes quite cumbersome.
That's why adding a interactive or graphic menu Above all: the logic is still in bash, but the user experience is greatly improved, especially for people less comfortable with the console.
Bash concepts you'll need before adding menus

To prevent your menus (whether text or graphic) from becoming chaotic, it's essential to control a few things. bash fundamentalsParameters, arrays, control structures, operators, redirections, and functions. You don't need to be a guru, but you do need to be comfortable with them.
In terms of parameters, bash provides a number of very useful special variables: $# indicates how many arguments They've switched to the script, $0 contains the name of the script itself, $1…$9 are the first nine parametersAnd with ${N} You can access any position. To manage them in bulk, you have $* y $@And with $$ You get the script's PID. The exit code of the last command is saved in $?.
A key command in scripts that accept many arguments is shiftIt allows you to shift parameters one position backward, so you can process options such as --nombre o --apellido in any order without driving yourself crazy. It's typical in scripts that accept GNU-style flags and where you want the user to be able to enter them in any order they like.
The variables In bash, variables are not typed: they can store text, numbers, arrays, anything you want. You can assign values directly. VAR=texto or build them from the output of a command using VAR=$(comando) or the old way with backticks. Watch out for the single and double quotation marksSingle quotes do not expand variables, while double quotes do.
Another pillar is the arraysYou can declare them with ARRAY=(a b c)assign individual elements with ARRAY[0]=valor and access with ${ARRAY[n]}In addition, you have access to utilities such as ${ARRAY[@]} for all elements, ${#ARRAY[@]} to find out how many there are or ${!ARRAY[@]} to obtain the active indices, something very useful when the array has "gaps".
Finally, the control structures Classic statements (if, case, for, while, until, select) allow you to make decisions and build basic menus without yet relying on external tools. The combo box select + case It is especially useful for creating very fast console menus.
Building bash menus in the terminal (without a graphical interface)

Before making the leap to a full graphical menu, it's worth mastering the interactive text-mode menus using exclusively bash. They are lightweight, they work by SSH Without any drama and, if well made, they are very pleasant to use.
The most direct way is to display a list of options with threw out, read the choice with read and then use a CASE to execute the corresponding action. It's a typical template for maintenance or backup scripts where you have several predefined tasks.
Imagine you have a script that performs four types of backup to 7z (tmp, Dropbox, Documents and Android Studio). Instead of running everything at once, you can set up a simple menu to choose which copy you want to make:
#!/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
This transforms the script from a "black box" into a... clear text interfaceIf you want something more polished, bash offers the build. select, which generates a numbered list of options and manages the reading of the selection for you.
A slightly more advanced example using select could be:
#!/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
As you can see, with very little code you have a repeatable menu where the user can choose several options in succession until they select "Exit". This pattern can be reused in virtually any script where you want to offer several grouped operations.
Dialog and whiptail: “graphical” menus in text mode
When you want to go a little further in the terminal, tools like Dialogue y whiptailThey still work on a text console, but allow you to build windows, menus with cursors, checklists, progress bars and more, all controlled from the script using arguments.
Dialogue It's very well-known in the GNU/Linux world: many installers, like Slackware's, are built as a set of bash scripts supported by dialog. With it, you can create everything from simple informational messages to complex forms. Its syntax always revolves around a command like `/etc/bash`. dialog --opcion ... which returns the result via stdout or through the exit code.
A classic example of a menu with cursors in a dialog box would be:
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"
The option –stdout This sends the user's choice to standard output, so you can save it directly to a variable. $(...)The figures 12 40 4 They indicate height, width, and maximum number of visible items in the menu.
In addition to the simple menu, dialog supports msgbox (informational messages), yes no (confirmations), inputbox (text entries), checklist (lists with boxes), radiolist (single selection) and gauge (progress bars), among many other types. It's worth taking a look at the the Commission or the official documentation to see the full range.
whiptail It's a lighter alternative based on the newt library. It offers a set of dialogs very similar to dialog, with almost identical syntax, but is sometimes preferable because it consumes fewer resources or is already available in minimalist environments.
A menu built with whiptail would look like this:
#!/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
The trick of 3>&1 1>&2 2>&3 It is often used with whiptail to redirect the selection output to the appropriate descriptor so it can be captured with $(...)Although it may seem like gibberish, in practice you always reuse it the same way.
Dialog and whiptail in detail: messages, lists, and progress bars
Once you have installed dialog or whiptail (on Debian/Ubuntu with a sudo apt-get install dialog o sudo apt-get install whiptailand in RHEL/CentOS via yum with the corresponding packages), you can start building increasingly user-friendly interfaces.
For example, a simple message Using dialog is as simple as:
#!/bin/bash
dialog --title "Mensaje" \
--msgbox "Hola, este es un cuadro de mensaje simple." 6 50
If what you want is a main menu With several actions, you have a typical template like this:
#!/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"
The multiple selection lists They are built with --checklistA typical example would be an installer that lets you choose which packages to install:
#!/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"
The word off indicates that the box is unchecked at the beginning, whereas in whiptail it is usually used ON / OFF in uppercase. The result format is usually a list of selected items separated by spaces or quotation marks, depending on the configuration.
To show the progress of a long task, dialog provides –gaugeThe idea is to send it numerical values (0-100) through standard input while your script does the actual work:
#!/bin/bash
{
for ((i = 0; i <= 100; i+=10)); do
sleep 1
echo $i
done
} | dialog --title "Progreso" --gauge "Instalando..." 10 70 0
whiptail offers something almost identical using its own option –gaugeIn both cases the logic is the same: a loop that updates the percentage and feeds it through a pipe to the progress bar.
Real graphical interfaces with Zenity (GTK) for your scripts
If you work in a graphical server environment (Gnome, KDE, etc.) and want to take it a step further, you can use zenity to construct GTK dialog boxes directly from bash. Zenity is designed precisely for that: to give a "graphical face" to your scripts without the need to program a complete application.
Installation on Debian/Ubuntu is as simple as:
sudo apt-get install zenity
In Red Hat-based systems the idea is the same, usually with sudo yum install zenity or the equivalent package. Once installed, you call it from the terminal or from your script with commands like zenity --info ....
Zenity features several types of dialogue: info, warning, error, question, file selection, forms, lists, progress bars and more. For example, a simple informational message:
zenity --info --text="Información"
Either a warning or an error:
zenity --warning --text="Advertencia"
zenity --error --text="Error"
The grace lies in save the path in a variable Capturing Zenity's output within the script. If you want the user to select a file and save the path in a variable, you could do something like this:
file=$(zenity --file-selection)
From that moment on you can work with $file Just like with any route entered via the keyboard. It's a great way to request parameters without having to type endless paths in the terminal.
Advanced examples: notifications, console bars, and web automation
Besides menus, bash works really well with a whole host of small utilities These allow you to create fairly elaborate solutions without leaving the console ecosystem. Some practical examples that work well with menu-based interfaces are listed below.
For web interaction you can use text browsers such as lynx or downloaders like wgetA real-world example: checking for new messages in the forums of a project hosted on SourceForge. The script downloads the page using Lynx, extracts the lines containing "Last Action", saves them to a file, and compares them to the last execution using... diffIf there are changes, you know there's something new to look out for.
Another typical example is automating compilation or development tasks. For example, compiling small OpenGL/GLUT programs for Windows under Linux using MinGW and wine with a single line of script, automatically generating the executable name from the .cpp file name with the help of thirst and string substitutions.
The desktop notifications They're also a great complement to scripts that run "in the background" while you do something else. The tool notify-send (from the package) libnotify-bin (in many distros) lets you launch notification balloons in the taskbar, ideal for notifying users that a copy of a DVD with dd has ended, for example:
notify-send -t 4000 "Terminó!"
If you prefer text-based but visually appealing interfaces, you can combine multiple utilities such as df, ls, bc, ps, xrandr or tput to display progress bars in the console, animations (like a snowfall with characters), or even scripts that calculate the screen resolution and use it to record screencasts with ffmpeg.
Advanced Bash: Redirections, Pipes, and Error Handling
Behind any decent menu there is usually good management of standard input and outputIn Linux, everything passes through three descriptors: stdin (0), stdout (1), and stderr (2). Depending on how you redirect each one, you can save results to files, hide errors, mix them, or chain commands together using pipes.
The basic redirection > sends stdout to a file (overwriting it), while >> Add it to the end. If you want to separate results and errors, you have syntax like this: 1>fichero y 2>erroresYou can also send errors to the same place as the output with 2>&1 or vice versa.
A commonly used pattern is to generate a list and silently save only the result, ignoring errors by redirecting them to / Dev / null. For example: uterine
ls -1 /tmp/Carpeta1 1>/tmp/lista_ficheros.txt 2>/dev/null
The pipelines (|These are the other important component. They allow you to pass the output of one command to the input of the next, forming very powerful chains. A classic example: sorting a list of files with sort or calculate the screen resolution with xrandr | grep | awk chained together. This same mechanism is used to feed progress bars in dialog/whiptail or Zenity forms.
As to error controlAll commands return an exit code accessible with $?Well-written scripts usually store that value in a variable and return it at the end with exit, allowing the system or other scripts to know if everything went well (code 0) or if something failed (any value other than 0).
Within functions, the equivalent of exit es returnwhich also only accepts numbers. If you want to return other data types from a function (text, arrays), the usual practice is to use global variables or write to stdout and capture the output with $(funcion).
Adding a graphical launcher icon to a bash script in Debian
Another very common case when you start mixing bash with a graphical interface is wanting to launch a script from an icon in Gnome or from the file manager, without having to open a terminal and type the command manually.
For example, imagine you have a script myscripts/doom2.sh what runs chocolate-doom -iwad WAD/DOOM2.WADHave you tried it in the terminal with bash myscripts/doom2.sh And it works perfectly, but you want Start it with a double-click or from a desktop launcher in Debian with Gnome.
In Gnome-based environments, it is usually sufficient to create a .desktop file en ~/.local/share/applications (for the current user) or in /usr/share/applications (at the system level), with content similar to this:
[Desktop Entry]
Type=Application
Name=Doom 2 (Chocolate Doom)
Exec=/ruta/completa/myscripts/doom2.sh
Icon=application-x-executable
Terminal=false
Categories=Game;
After granting permissions and updating the application index, you can find it in the menu or pin it to the dock. If you prefer to launch it by double-clicking from the file manager, make sure the script has execute permission and that the environment is configured to "run" rather than "open" text scripts.
Combining this with tools like zenity You can create small, homemade desktop “applications” that are nothing more than bash scripts with a couple of dialog boxes on top.
With everything we've seen, you now have the key pieces on the table to go from plain console scripts to utilities with graphical or pseudo-graphical menus that anyone can handle without any problems: simple menus with echo/read, rich menus with dialog and whiptail, GTK boxes with Zenity, notifications, progress bars, and a solid foundation in bash: parameters, arrays, redirects, functions, and error handling. From here, it's time to experiment, adapt the examples to your needs, and refine the user experience of your scripts until they feel like fully functional little applications.
Passionate writer about the world of bytes and technology in general. I love sharing my knowledge through writing, and that's what I'll do on this blog, show you all the most interesting things about gadgets, software, hardware, tech trends, and more. My goal is to help you navigate the digital world in a simple and entertaining way.