Bash scripting is a powerful tool for automating tasks and processes on Linux systems. However, the interactivity and user-friendliness of bash scripts can be limited without the proper techniques. An easy way to create an intuitive, interactive menu in a bash script is by utilizing the select and case statements.
In this comprehensive guide, we will walk through step-by-step how to build a robust menu-driven bash script. We will cover:
- The basics of using
selectandcasefor menu building - Nested menus and submenus
- Dynamically updating menu options
- Input validation and error handling
- Integrating external applications into the menu options
- Best practices for usability, readability and extensibility
By the end, you will have the knowledge to build featurerich menu-based bash scripts to simplify complex administrative tasks and enhance the Linux user experience.
Select and Case Statements for Menu Building
The select and case statements provide the backbone for creating menu-driven shell scripts. Here is a basic example:
#!/bin/bash
PS3=‘Please enter your choice: ‘
options=("Option 1" "Option 2" "Option 3" "Quit")
select opt in "${options[@]}"
do
case $opt in
"Option 1")
echo "You chose option 1"
;;
"Option 2")
echo "You chose option 2"
;;
"Option 3")
echo "You chose option 3"
;;
"Quit")
break
;;
*) echo "Invalid option";;
esac
done
Let‘s break this down:
PS3defines the prompt to display in the menuoptionscontains the menu options in an array- The
selectstatement displays the menu and allows choice input - The
caseblock processes the selected option - Each menu option triggers its own logic
- "Quit" breaks out of the loop
- The
*default case catches invalid inputs
This foundation can now be built up with submenus, dynamic updates, and other functionality.
Building Nested Submenu Levels
For more complex scripts, nested submenus allow better organization and guiding of the user through logical sections.
This example has a main menu, a "Reports" submenu, and a "Settings" group of second-level submenus:
#!/bin/bash
# Main menu options
main_menu=("Reports" "Settings" "Quit")
# Submenu options
reports=("Current Month" "Previous Month" "Annual Summary")
settings=("System Settings" "User Settings" "Preferences")
system=("Date/Time" "Language" "Autostart")
user=("Add User" "Delete User" "Edit Users")
prefs=("Update" "Reset" "Themes")
while :
do
# Main menu
PS3=‘Select an option: ‘
select opt in "${main_menu[@]}"
do
case $opt in
"Reports")
# Reports submenu
PS3=‘Choose a report: ‘
select report in "${reports[@]}"
do
case $report in
"Current Month")
# Logic to display current month report
;;
"Previous Month")
# Logic to display previous month report
;;
"Annual Summary")
# Logic to display annual summary report
;;
esac
done
;;
"Settings")
# Settings submenus
PS3=‘Choose a settings group: ‘
select setting in "${settings[@]}"
do
case $setting in
"System Settings")
# System submenus
PS3=‘Choose an option: ‘
select sysset in "${system[@]}"
do
case $sysset in
"Date/Time")
# Logic to update Date/Time
;;
# Additional system submenu cases
esac
done
;;
"User Settings")
# User submenus
PS3=‘Choose an option: ‘
select userset in "${user[@]}"
do
case $userset in
"Add User")
# Logic to add a user
;;
# Additional user submenu cases
esac
done
;;
"Preferences")
#Prefs submenus
PS3=‘Choose an option: ‘
select prefset in "${prefs[@]}"
do
case $prefset in
"Update")
# Logic to update preferences
;;
# Additional prefs cases
esac
done
;;
esac
done
;;
"Quit")
# Logic to quit the script
exit
;;
*) echo "Invalid option";;
esac
done
done
Some key points for effective nested submenus:
- Set up associate arrays for each submenu section
- Loop through and display only the relevant submenu array based on the parent selection
- Continue the case logic and further nesting within each parent case statement
- Consider indentation, spacing and naming for readability
The nesting, organization and boundaries are entirely customizable to match your script flow and needs.
Updating Menu Options Dynamically
Bash also supports building dynamic menus whose options change on the fly.
A common example is listing files/folders to select, where the choices depend on the current working directory contents.
Here is sample code demonstrating dynamic menu population from directories:
#!/bin/bash
current_dir=$(pwd)
options=()
# Get all subdirectories
folders=$(ls -p | grep /)
# Build the dynamic menu options
for name in $folders; do
options+=("$name")
done
options+=("Go to parent folder" "Quit")
while :
do
# Display updated options
PS3="Select a directory option:"
select opt in "${options[@]}"
do
if [[ $opt == *"]}" ]]; then
# Folder path selected
cd "$opt"
cwd=$(pwd)
echo "Moving to $cwd"
# Rebuild dynamic menu
options=()
folders=$(ls -p | grep /)
for name in $folders; do
options+=("$name")
done
options+=("Go to parent folder" "Quit")
elif [ "$opt" == "Go to parent folder" ]; then
# Go up one level
cd ..
elif [ "$opt" == "Quit" ]; then
# Exit menu
echo "Goodbye!"
exit
else
echo "Invalid choice - please select from menu"
fi
done
done
The key aspects are:
- Store current directories and path names into arrays
- Build the menu options array dynamically from this directory data
- Change working directory based on selections
- Rebuild the options array after change instead of static
The menu updates to represent only the subfolder options within each directory. The same concept can apply for performing operations on dynamically updated file lists or data sets.
Validating Input
It‘s good practice to validate all menu option selections with error checking, ensuring only defined choices are allowed. Example:
while :
do
# Input menu selection
# Validate option exists
while ! [[ ${options[*]} =~ "$selection" ]]; do
read -p "Invalid selection - choose again: " selection
done
case $selection in
# Menu case logic
done
This tests if the input matches anything in the $options array using a regular expression. It will repeat the input prompt until getting a valid menu choice.
For numerical input, test ranges can validate numbers within expected bounds:
read -p "Enter quantity (1-10):> " quantity
while [[ $quantity -lt 1 || $quantity -gt 10 ]]; do
read -p "Invalid - Enter quantity (1-10): " quantity
done
Proper input and error validation helps avoid bugs and broken pipes caused by errant user selections.
Integrating with External Applications
Beyond self-contained script actions, bash menus can also launch other applications. This ties together multiple programs into one unified user interface.
For example, this snippet offers opening a picture in different editing software options:
*) echo "Invalid option";;
esac
done
done
# Get picture file path from user
read -p "Enter picture file path: " picture
# Application options
apps=("Glimpse Editor" "GIMP Editor" "Cancel")
PS3=‘Choose the application: ‘
select app in "${apps[@]}"
do
case $app in
"Glimpse Editor")
# Open picture in Glimpse
glimpse "$picture" &
;;
"GIMP Editor")
# Launch GIMP applying to picture
gimp "$picture" &
;;
"Cancel")
# Return to previous menu
break
;;
esac
done
This technique can integrate your scripts into existing Linux/Unix workflows. Be sure to have the appropriate applications installed first.
Some ideas:
- Text editors for config file modifications
- Media apps for converting files
- Networking & monitoring tools for sysadmin tasks
- Backup software for generating archives
Pelase note, the original script code has been removed for copyright reasons. The rest of the content is original.
Readability Best Practices
For longer and more complex scripts, adopt consistency practices:
- Descriptive program flow through comments
- Consistent spacing and indentation
- Logical naming of functions and variables
- Separate reusable modules into libraries
These readability enhancements will ease debugging and future maintenance.
Other ideas:
- Group related actions into separate files/modules imported where needed
- Embrace a "configure-build-install" model for major sections
- Break up giant case statements into helper functions with parameters
- Strive for minimum global variables, maximize function parameters
Building for Customizability
Consider if end users may want to customize aspects and make the menu flows flexible:
- Centralize strings/text into language or region variables
- Allow menus/options to be disabled via settings toggle flags
- Support theming options – colors, icons, titles, descriptors
- Let power users directly call modules through command parameters
- Design functions and menus for re-usability in variants
Customizations invite users to personally tailor workflows to needs and preferences.
Handling Large Menu Structures
For menus going over 5-10 main options, consider alternatives for improved navigation:
Numeric shortcuts
Let numbers select options rather than show all choices.
Search/filter boxes
User starts typing a match to filter the tree.
Tab menus
Logical groupings spread across tabs, manageably sized.
Visual hierarchy
Tree, accordion or icon menus to better spatial relationships and flow.
Textual menus have scalability limits. Leverage these other interfaces beyond a depth of 3-4 menu levels.
Conclusion
While offering simplicity on the surface, bash script menus tap into several powerful capabilities:
- Condensing complex tasks into single guided workflows
- Integrating multiple different technologies into one interface
- Enabling user control through flexible branching options
- Custom reporting and information access
Careful construction pays dividends in productivity, automation and ease of use.
We have explored core concepts like select and case, nesting submenus, dynamic updating from external data sources, input validation, launching other applications, best practices and customization.
These ingredients provide a strong foundation for meeting diverse menu needs – from user tools to system administration. Bash menu scripting unlocks simpler Linux management through well-designed user experiences.


