How to Implement a Multi-Select Menu Interface in Bash Scripts


1 views

#!/bin/bash

# Initialize an array to store selected options
declare -a selected_options
menu_items=("Option 1" "Option 2" "Option 3" "Option 4" "Done")

while true; do
    clear
    echo "Please select options (multiple selections allowed):"
    
    # Display menu with selection indicators
    for i in "${!menu_items[@]}"; do
        # Check if current option is selected
        if [[ " ${selected_options[*]} " == *" ${menu_items[$i]} "* ]]; then
            printf "%d) %s +\n" $((i+1)) "${menu_items[$i]}"
        else
            printf "%d) %s\n" $((i+1)) "${menu_items[$i]}"
        fi
    done

    read -p "Your choice: " choice
    
    # Validate input
    if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt "${#menu_items[@]}" ]; then
        echo "Invalid option. Please try again."
        sleep 1
        continue
    fi

    selected_index=$((choice-1))
    selected_item="${menu_items[$selected_index]}"
    
    # Handle Done selection
    if [ "$selected_item" == "Done" ]; then
        clear
        if [ ${#selected_options[@]} -gt 0 ]; then
            echo "You selected:"
            printf "%s\n" "${selected_options[@]}" | sed ':a;N;$!ba;s/\n/, /g'
        else
            echo "No options were selected."
        fi
        exit 0
    fi
    
    # Toggle selection
    if [[ " ${selected_options[*]} " == *" $selected_item "* ]]; then
        # Remove from selected options
        selected_options=("${selected_options[@]/$selected_item}")
        # The above leaves empty elements, so we need to compact the array
        temp=()
        for item in "${selected_options[@]}"; do
            [[ -n $item ]] && temp+=("$item")
        done
        selected_options=("${temp[@]}")
    else
        # Add to selected options
        selected_options+=("$selected_item")
    fi
done

This script demonstrates several important bash concepts:

  1. Array manipulation for tracking selected items
  2. Input validation to prevent errors
  3. Interactive menu refreshing using clear
  4. String manipulation for toggling selections

For more complex implementations, you might want to add:


# Color highlighting for selected items
printf "\033[32m%d) %s +\033[0m\n" $((i+1)) "${menu_items[$i]}"

# Multi-number input support (e.g., "1 3 4")
read -p "Your choices (space separated): " -a choices
for choice in "${choices[@]}"; do
    # Process each choice
done

# Persistent selection display during input
read -p $'\033[7m'"Your choice (current: ${#selected_options[@]} selected)"$'\033[0m: ' choice

When working with bash menus:

  • Always validate input to prevent script errors
  • Use arrays carefully - bash array handling can be tricky
  • Consider terminal compatibility issues with clear/formatting
  • For production use, add timeout/quit options

For more sophisticated interfaces, consider:


# Using dialog or whiptail for native terminal dialogs
selected=$(dialog --checklist "Choose options:" 15 40 4 \
    1 "Option 1" off \
    2 "Option 2" off \
    3 "Option 3" off \
    4 "Option 4" off 2>&1 >/dev/tty)

Or external tools like fzf for fuzzy-selection interfaces.


While bash's built-in select statement provides basic menu functionality, it lacks native support for multiple selections. Here's a robust implementation that overcomes this limitation while maintaining clean script execution.

This solution uses associative arrays (requires bash 4.0+) to track selections and dynamically updates the menu display:

#!/bin/bash

# Options configuration
options=("Option 1" "Option 2" "Option 3" "Option 4" "Done")
declare -A selected

# Display function with toggle indicators
display_menu() {
  clear
  echo "Select options (multiple allowed):"
  for i in "${!options[@]}"; do
    marker=$([[ -n "${selected[$i]}" ]] && echo " +" || echo "")
    printf "%d) %s%s\n" $((i+1)) "${options[$i]}" "$marker"
  done
}

# Main selection loop
while true; do
  display_menu
  read -p "Your choice: " choice
  
  # Validate input
  if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#options[@]} )); then
    echo "Invalid option"
    sleep 1
    continue
  fi
  
  # Handle Done selection
  if (( choice == ${#options[@]} )); then
    break
  fi
  
  # Toggle selection state
  idx=$((choice-1))
  if [[ -n "${selected[$idx]}" ]]; then
    unset "selected[$idx]"
  else
    selected[$idx]=1
  fi
done

# Output selected items
selected_options=()
for i in "${!selected[@]}"; do
  selected_options+=("${options[$i]}")
done

echo "Selected options: $(IFS=,; echo "${selected_options[*]}")"

For a more polished implementation, consider adding:

  • Color highlighting for selected items
  • Input validation for non-numeric entries
  • Horizontal display for space efficiency
  • Support for checkbox-style toggling (using ANSI characters)

If your system has dialog installed, you can leverage its built-in checklist widget:

#!/bin/bash

dialog --clear --checklist "Select options:" 15 40 10 \
  1 "Option 1" off \
  2 "Option 2" off \
  3 "Option 3" off \
  4 "Option 4" off \
  2> selections.txt

selected=$(cat selections.txt)
echo "You selected: $selected"
rm -f selections.txt

For scripts with dozens of options, consider:

  • Implementing pagination
  • Adding search/filter capability
  • Optimizing screen redraws

Always include proper error handling:

if (( ${#selected_options[@]} == 0 )); then
  echo "No options selected" >&2
  exit 1
fi