- Menus in Bash are built by combining structures like read, case, and select, allowing you to choose actions without remembering them. commands complex.
- Organizing code into independent functions (create, validate, delete, list) makes it easier to maintain and extend scripts with interactive menus.
- Tools like dialog add advanced text interfaces with boxes and cursor navigation, ideal for more visual menus in terminal.
- Integration with cron and good debugging practices make these menus robust solutions for automating and controlling tasks in Linux.

If you work with GNU/Linux daily, sooner or later you'll wonder how automate repetitive tasks without losing control over what is being executed. That's where the Bash scripts with option menus: small programs in the terminal that ask you what you want to do and act accordingly.
Far from being something reserved for console gurus, creating these menus is quite accessible. With a few basic Bash constructs—select, CASE, readloops and some extra utility such as dialog for graphical menus— you can mount anything from a simple numeric selector to interactive text interfaces with cursor arrows, pop-up boxes, and data validations.
Basic concepts: what is a Bash script with an options menu
Un script Bash is nothing more than a text file with commands which the interpreter executes one after the other. The advantage of adding a menu is that instead of always launching the same sequence, the user can choose which action to perform at any time, by typing a number, moving with the arrows, or selecting a specific entry.
In this context, a menu of options in Bash can take several forms: from the classic numbered list with read + case...even full-screen dialog boxes with the utility Dialogue, going through the automatic menus provided by the keyword select integrated into Bash itself.
These types of scripts are used, for example, to manage users from a simple application, launch backups of different folders, display system utilities (processes, calendar, hostname) or encapsulate several administrative tasks under one unified and user-friendly interface on the console.
Beyond convenience, these menus are useful because they allow people with less experience in the command line to... perform complex actions without remembering routes, options or parameters, reducing errors and saving time.
Simple menus with read and case: the most direct approach
The most direct way to build a menu in Bash is to display a numbered list of options, read a key or a number with read and, depending on what the user has entered, execute one block of code or another using a structure case ... esac.
Imagine you have a backup script that compresses four different folders with 7zWithout a menu, the script would simply execute all the commands at once, generating four files of backup every time you run it, even if you only want to update one of them.
The solution is to group those commands into a single script and add a small menu to it. choose which backup to launchThe basic pattern that is usually used is:
1) Define the necessary variables, such as the current date, to name the backup output files.
2) Show options with multiple echoFor example, one header line and four lines for each task: temporary folder, Dropbox, Documents, and Android Studio
3) Read the selected option from the keyboard, using read to capture a single character or number, which will then be stored in a variable (for example, n).
4) To use a sentence case $n in ... esac to decide which command to execute based on the value: each branch of the case contains the order 7z corresponding to the chosen folder, and a wildcard case is also defined *) To catch invalid options and notify the user.
Practical example: backup menu in Bash
A very typical case in home or small office environments is having a script that launches manual backups to an external drive. Instead of maintaining four separate scripts or always running all backups, we can centralize the logic in a single interactive menu.
The skeleton of the example script usually begins with the shebang line #!/bin/bash, followed by the date setting:
DATE it is obtained with date +"%Y-%m-%d"so that all generated files are identified by day, which is very practical for accumulating historical copies.
The user is then presented with a block like this (written here for illustrative purposes): one line with the text and four subsequent lines numbered 1 to 4, each describing a folder: tmp, dropbox, documents y androidThose descriptions serve to make it It's clear what will be supported in each case.
With a simple read n The choice is captured. From there, a case $n in allows you to associate each number with the specific command of 7z which compresses the appropriate folder on the external drive. Using solid compression with -mx9 and a password with -pContraseña It adds security and efficiency, and the naming scheme with date and folder suffix (for example, $FECHA-tmp.7z) helps to easily locate each file.
The great advantage of this approach is that, without duplicating logic or maintaining multiple files, we enable the user to execute only the task that interests youAnd if one day we want to add a new backup folder, we simply need to add a new option to the menu and its branch to the case.
Advanced menus with select: taking advantage of Bash's own features
Bash includes a very convenient keyword for automatically generating numbered menus: selectWith it, you don't need to write everything by hand. echo to display the options or manage the index numbers; the interpreter itself handles that Print the menu and capture the selection..
The typical structure of a menu with select It's something like this: an array is defined with the different alternatives, a special variable is set PS3 with the prompt text (for example, «») and then a loop is opened select opt in "1" "2" "3" "Quit"; do ... done.
Within the do ... done a is used again case $opt in to link each array value to a specific action. In the backup example, entries "1", "2", and "3" correspond to each set of folders or backups we want to run, and an additional option like "Quit" allows exit menu with a simple break.
The big difference compared to the approach with read es que select It automatically prints a list of numbered alternatives, and the user only has to type in the option number. Furthermore, Bash manages the variable REPLY with the entered value, which facilitates validation in more complex cases.
In slightly more elaborate menus, select This is especially useful because it reduces the amount of repetitive code and makes the script more efficient. more legible and maintainableespecially when the list of options grows with There.
Creating structured menus: an example of user management with functions
When the menu controls more delicate tasks, such as user account management In an internal application, it's advisable to organize the script into well-separated functions. One of the classic examples defines at least four functions: solicitar_usuario(), validar_usuario(), crear_usuario() y eliminar_usuario(), in addition to the main loop that displays the menu.
The function request_user() It handles interactively prompting the user for an account name and password, with double confirmation. Internally, it collects an optional message passed as a parameter to display different text depending on the context (first registration, duplicate user entry, password correction, etc.).
In that same function, it is used read -p to request the username and read -sp For the password, hiding the input in the terminal. Then, the user is asked to repeat the password in another variable, and a line break is displayed with echo "" to avoid leaving the prompt stuck to the previous text.
The second key piece is validate_user()which checks if the provided name already exists. It typically relies on a plain text file (for example, accesos/cuentaswhere one account is listed per line. By grep The identifier is searched for, and a control variable is established, such as validacion, to 0 or 1 depending on whether there has been a match.
This function returns its result using return, which allows it to be used in conditions of the type while validar_usuario "$usuario"; do ... done. Thus, create_user() can invoke solicitar_usuario as many times as necessary until the name is new and the passwords match.
Within create_user()After data collection, two loops are established: one that repeats as long as the account already exists in the file, displaying a message such as "The selected user already exists…", and another that repeats if the password does not match the confirmation, forcing the user to re-enter both fields. Once the validations are passed, the function adds the username to accesos/cuentas to echo "$usuario" >> accesos/cuentas and confirms on screen with a text such as "user created".
The function delete_user() It follows a similar philosophy, but focused on account deletion. First, it requests the name to be deleted with read -p, and then use a loop along with validar_usuario to ensure the account actually exists. If the user is not found, a "User not found" message is displayed and the function is exited. return 1.
When the account is valid, deletion is performed with a common trick: it is executed grep -Ev "$usuario" accesos/cuentas > accesos/cuentas_tmp to generate a temporary file without the corresponding line, and then rename it with mv accesos/cuentas_tmp accesos/cuentasFinally, the operation is reported with a message such as "user deleted".
Main loop of the script: build the management menu
Once the auxiliary functions have been defined, a Infinite loop which acts as the main menu. It is usually written as while true; do ... doneso that the script does not end after performing a single action, but returns to the list of options until the user chooses to exit.
Inside the loop, a call to clear Clean the screen to have a tidier environment. Then print with echo the menu title (for example, "User Management") and the various numbered options: create user, delete user, list users, and exit.
The option is selected using a read -sn1 opcionThis reads a single character with no echo (or minimal echo) and stores it in the corresponding variable. This allows the user to simply press a key from 1 to 4, without needing to confirm with Enter in some cases, depending on how it's combined.
Next, a echo "" to go down to the next line and enter the case block which handles each option. Branch "1" invokes crear_usuario, the "2" calls to eliminar_usuario, the "3" uses cat accesos/cuentas to show all users and then a read -n1 -p "" or similar to pause before returning to the menu, and the "4" executes exit 0 to exit the script.
After esacA short additional pause is usually included, such as read -n1 -p "Presione cualquier tecla"This prevents the screen from refreshing too quickly if the action ends without visible interaction (for example, after creating a user). This makes the flow more efficient. comfortable and intuitive for the person at the keyboard.
This loop + menu + case pattern is extensible to almost any task: as long as you have a well-defined set of actions that you want to present to the user, you can package them into functions and link them from a similar structure.
Use select to build more elegant backup menus
Returning to the example of backups, a more polished variant of the menu involves leveraging the keyword select instead of combining manually echo y readIn this case, the first thing is to fix the variable PS3 with the message that will appear as a prompt, for example: PS3="".
Then an array is defined, like options=("1" "2" "3" "Quit")and it is written select opt in "${options}"; do ... doneBash will automatically display a numbered menu with those four entries, and the user will choose by entering the number that corresponds to the one they want.
Inside the body of select a is implemented case $opt in to distinguish between the options. In BRANCH "1", the 7z command is executed to back up the temporary folder, in "2" the Dropbox backup, in "3" the Documents folder, and so on. When you reach the "Quit" option, use break to end the select and, consequently, stop showing the menu.
As before, a branch is included *) For unforeseen entries, a message like "Incorrect option" is printed if the user enters a value that doesn't fit in the list. This approach makes it easier group many maintenance tasks in a single script, without needing to remember individual file names.
Although conceptually it doesn't do anything that can't be done with read + case, select It reduces visual noise in the code and automatically manages some details, making it an interesting option when you want to keep the script clear and concise.
Interactive menus with dialog: “box” type interfaces in the terminal
If you want to go a step further, you can use text interface utilities such as DialogueThese tools allow you to display menus with borders, titles, buttons, and text-mode boxes, similar to older applications like ncurses. They are perfect when you're looking for a user-friendly experience. more visual and navigable with the keyboard.
With dialog you can create navigable menus with cursor arrows and keyboard shortcuts in LinuxA typical example is a script that displays a box with a title such as "Utilities" or "MAIN MENU," a list of options ("Processes," "Calendar," "Host," "Exit"), and an additional help button. The user can navigate using the arrow keys, highlight the letters, or simply press the numbers 1 through 9 to select.
The general mechanics of this type of script are as follows: a temporary file is defined to store the chosen option (for example, INPUT=/tmp/menu.sh.$$) and another to capture the output of the commands that will be displayed in text boxes (OUTPUT=/tmp/output.sh.$$), wearing the variable $$ to differentiate each process.
Through instruction trap It is configured so that, upon receiving certain signals such as SIGHUP SIGINT or SIGTERMThe script executes a cleanup routine that deletes these temporary files and exits gracefully. This prevents residual files from remaining on the system if the user interrupts the script with Control+C or if the process terminates abruptly.
A function like showOutput() It encapsulates the logic for displaying a message box with a dialog. It collects parameters such as the window's height, width, and title, and then invokes the function. dialog --backtitle "Utilidades" --title "${titulo}" --clear --msgbox "$(cat $OUTPUT)" altura ancho (or equivalent variant) to present the OUTPUT content in a clean box.
Other specific functions, such as showHost() o showCalendar()They generate the information that you want to visualize. For example, muestraHost() run hostname and redirects the result to $OUTPUT, and then call muestraSalida 6 40 "Nombre del Host". In the same way, muestraCalendario() run cal and displays a small monthly calendar in a box titled "Calendar".
In the main loop, dialog --menu It is responsible for building the actual options menu. Parameters such as background title are specified (--backtitle), box title (--title), descriptive text for using the arrows and numbers, dimensions (height, width, maximum number of visible elements) and finally the "label"-"description" pairs for each menu option.
The user's choice is stored by redirecting the dialog's standard output to the input file (2>"${INPUT}"Then that file is read with something like opcionesMenu=$(<"${INPUT}") and a case optionsMenu in to trigger the corresponding function: show processes, show calendar, show hostname, or exit menu.
When the loop ends (when the user chooses "Exit"), the script deletes the Temporary files if they still exist, using checks like && rm $OUTPUT and the same with $INPUTIn this way, the system is cleaned and prepared for future executions.
Menus with select plus dialog: selection using cursors
In addition to using dialog boxes for message boxes, there is a very interesting integration: creating menus that not only accept numbers, but also allow moving a cursor through the various options, as if it were a drop-down menu of applications like Midnight Commander.
Utility Dialogue It includes, among other things, a specific type of menu box: --menuWith a call like this 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")A box with a title and a numbered list is displayed on the screen; the user moves with the arrows, selects an entry, and the dialog returns the associated value (for example, "1", "2", etc.), which is stored in the variable respuesta.
Thanks to the option --stdoutThe selection is written to standard output and can be captured directly in a command substitution like the one above. From there, simply use a case "$respuesta" in to act according to the chosen option. This variant is especially useful in connections SSH in text mode, where there is no graphical interface but a comfortable interactive experience is desired.
In larger scenarios, it's common to combine dialog with Bash functions and, if needed, with other languages such as Python For more complex tasks, for example, building Telegram bots where the options menu travels within the chat itself, but the selection and treatment logic It follows a scheme very similar to that of terminal menus.
The "pure" select command in Bash for lightweight menus
Although we have already seen practical uses of selectIt is worth taking a closer look at the basic syntax of the command as provided by Bash, because it is a very lightweight tool for building simple menus without external dependencies.
The generic structure is: an array with the options is defined, and then it is written select opt in "${opciones}"; do ... doneEach time the user enters a number, Bash assigns the corresponding element of the array to the variable. opt and the value introduced in crude oil to $REPLY.
A typical menu might present a message like "Select an option:" and a set of alternatives: "Option 1", "Option 2", "Option 3", and "Exit". Within the loop, a structure case $opt in It allows you to assign the corresponding action to each of those strings: display a message, execute a function, invoke another script, etc.
To close the menu, it is set that when the user chooses "Exit", a break that breaks the loop selectAdditionally, the branch can be used *) to intercept erroneous inputs (e.g., out-of-range numbers) and display a warning such as "invalid option $REPLY".
This menu format is especially suitable when you want something very portable and unadornedIt requires no extra utility beyond Bash, works equally well in graphical environments or over SSH, and is expressive enough to cover most basic interactive flows.
Basic Bash programming skills you need for your menus
To get the most out of all the above, it's helpful to master the fundamentals of programming in Bash. First, the use of shebang (#!/bin/bash or the corresponding path in your system) and end-of-line handling (CRLF vs LF) that affect portability.
It is also important to understand the execution permissionsA script will not run directly until you assign the execute bit to it with a command like chmod u+x nombre_script.shFrom there, you can call him with ./nombre_script.sh or by bash nombre_script.shas you prefer.
Regarding input and output, commands such as echo They print text strings or the value of variables, read It allows collecting user information, and redirection with > y >> It allows you to write or add content to files. Learning to manage these redirects will be key if you want your menu to function properly. manage configuration filesuser lists or logs.
Variables in Bash don't have strict types: they can contain numbers, text, or any string without the interpreter making much of a distinction. They are simply declared as nombre=valorand are referenced with $nombreIt is advisable to follow a clear and descriptive naming style, starting with a letter or underscore, avoiding spaces and reserved words such as if, then o else.
Finally, control structures such as if ... then ... filoops while y for and sentences case They are the glue that holds together the entire flow of menus: deciding which option has been chosen, when to repeat a question, how to validate a field, or when to exit the script.
Automate script execution with cron and debug them when they fail
Once your Bash menus are working interactively, you might want to automate some of those tasks To run them automatically, for example, scheduling a nightly backup with certain default options. That's where [the following appears to be a separate, unrelated section:] cron, the classic task scheduler in systems like Unix.
The syntax of a cron entry follows this pattern: minuto hora día mes día_semana comandoSomething like 0 0 * * * /ruta/a/script.sh It launches the script every day at midnight, while */5 * * * * /ruta/a/script.sh It runs every five minutes, and 0 6 * * 1-5 It will run at 6 a.m. Monday through Friday.
To manage your scheduled tasks you use crontab -l (list current jobs) and crontab -e (edit). In these scripts you can reuse functions and logic from your menus, only in this case the options are set by command line arguments or internal variables, without user interaction.
When debugging Bash scriptsThere are several key techniques: activate set -x Near the beginning of the script, Bash prints each command it executes preceded by a "+", allowing you to follow along step by step. Add set -e It causes the script to terminate automatically if any command returns an exit code other than 0.
The variable $? It always contains the exit code of the last executed command, so you can use it in conjunction with conditional statements to detect errors: if ; then echo "Hubo un error."; fiAnd, of course, the debugging messages to echo Inserted at strategic points, they help to see the content of variables and the actual flow of execution.
When automation involves cron, reviewing system logs is essential. In distributions like Debian or Ubuntu, many cron-related messages end in /var/log/syslogFrom there you can check if the jobs have been launched, if the scripts have returned errors, or if there are permission problems.
With all these pieces, from the simplest menus with read and case even the most visually appealing interfaces with Dialogue, through the use of selectWith well-structured functions, cron automation, and basic debugging techniques, you have a huge range of possibilities at your fingertips to create Bash scripts with option menus that make your day-to-day work at the terminal much more comfortable and controlled, both for common user tasks and for small administration panels on servers and personal computers.
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.
