Shell script menu problem

I have tried searching the forum but i haven't found a solution for this.

I have a shell script that presents the users with menus. The menus branch out to sub menus. It is all hunky dory as long as i traverse forward. But if i am in a sub menu and return to the previous menu and choose any option, i am taken back to the sub menu. I can see where i am going wrong in my code as you can see highlighted below. But i cannot get around it unless i return to the main menu which has become quite cumbersome. Is there any way i can get around this by using goto labels? Any other modification of my code to get around this issue will be appreciated as well. Thanks in advance

 
function mainmenu {
echo "--------------- MAIN MENU -------------------"
echo " 1. CH1 "
echo " 2. CH2 "
echo " 3. CH3 "
echo "Choose 1-3"
read choice
case $choice in
1)
    while :
    do
    submenu # calling another menu here
    case $subchoice in
        1)
               do something
               ;;
        2) 
              do something
              ;;
        3)
              do something
              ;;
        4)
              do something
             ;;
        5)
             mainmenu
             ;;
        6)
             uexit                    #  calling the function to exit
             exit 1 
             ;;
        esac
       done
       ;;
2)
     while :
     do
               submenu2 # calling another menu here
            case $subchoice2 in
            1)
                   do something
                   ;;
           2)
                  do something
                  ;;
           3)
                 while :
                 do
                                submenu3 # calling another menu here
                         case $subchoice3 in
                         1)
                                  do something
                                  ;;
                        2)
                                 do something
                                 ;;
                       3)
                                do something
                                ;;
                      4)
                               do something
                               ;;
                      5)
                                      submenu2
                              ;;
                     6)
                              uexit
                              exit 1 
                             ;;
                     esac
                    done
                    ;;
          4)
                    do something
                     ;;
          5) 
                    mainmenu
                     ;;
          6)
                     uexit
                    exit 1  
                     ;;
          esac
        done 
       ;;
3)
      do something
       ;;
4)
      uexit
      exit 1
    ;;
esac
return
}
mainmenu

Without seeing what you're doing in your submenus, it's difficult to tell what's going wrong with them.

There may be a way to simplify this a lot with select. Working on it.

What's your system, what's your shell? Do you have functions?

Here's a long-winded way that may help point you in the right direction by putting the menus in functions:

#!/bin/bash


function main_menu()
{
    while :
    do
        echo
        echo "MAIN MENU"
        echo
        echo "1. Main Menu Option 1"
        echo "2. Main Menu Option 2"
        echo "3. Main Menu Option 3 To Sub-Menu"
        echo "4. Main Menu Option 4 To Quit"
        echo
        read -p "Choice: " m_choice
        echo
        
        case "$m_choice" in 
            1)  echo "Main Menu Option 1"
                echo
                ;;
            2)  echo "Main Menu Option 2"
                echo
                ;;
            3)  sub_menu
                ;;
            4)  exit 0
                ;;
            *)  echo "Bad Option"
                echo
                ;;
        esac
    done
}

function sub_menu()
{
    while :
    do
        echo
        echo "SUB MENU"
        echo
        echo "1. Sub-Menu Option 1"
        echo "2. Sub-Menu Option 2"
        echo "3. Sub-Menu Option 3 Back To Main Menu"
        read -p "Choice: " s_choice
        echo

        case "$s_choice" in
            1)  echo "Sub-Menu Option 1"
                echo
                ;;
            2)  echo "Sub-Menu Option 2"
                echo
                ;;
            3)  main_menu
                ;;
            *)  echo "Bad Option"
                echo
                ;;
        esac
    done
}

main_menu

exit 0

Hope this helps.

Here's an example using select and the argument array to easily switch between many menus. The same code prints all the menus and reads all the replies using the select builtin.

We use the the $1 variable here to define which menu it's currently showing, which you can alter at will with the set -- and shift commands. You can easily use it like a stack, with set -- nextmenu "$*" [b]pushing a menu to show next and [b]shift popping a menu to return to the previous one.

But set -- will let you set menus in whatever arbitrary order you want, too.

#!/bin/bash

IFS="|" # Split menus on |, so we can have spaces in titles

# Use parameters as a stack.  $1 is the current menu.
# we can add with set -- again, or remove with 'shift'.
set -- main

while [ "$#" -gt 0 ]
do
        case "$1" in    # Look up the right options for the menu
        main)           OPTIONS="CH1|CH2|CH3|Q" ;;
        submenu1)       OPTIONS="A|B|C|Q"       ;;
        submenu2)       OPTIONS="D|E|F|Q"       ;;
        submenu3)       OPTIONS="X|Y|Z|Q"       ;;
        esac

        PS3=$'\n'"$1 menu: "    # Special variable controlling select prompt

        # Print menu, get reply
        select X in $OPTIONS
        do
                [ -z "$X" ] || break
        done

        # We treat Q specially, it returns to the previous menu.
        if [ "$X" = "Q" ]
        then
                shift           # Return to previous menu
                continue        # Go to top of loop
        fi

        case "$1" in
        main)
                case "$X" in
                CH1)    set -- submenu1 "$*"    ;;
                CH2)    set -- submenu2 "$*"    ;;
                CH3)    set -- submenu3 "$*"    ;;
                esac
                ;;

        submenu1)
                case "$X" in
                A)     # Switch to submenu2 by removing submenu1 before adding
                        shift
                        set -- submenu2 "$*"
                        ;;
                B)      ;;
                C)      ;;
                esac
                ;;

        submenu2)
                case "$X" in
                A)      ;;
                B)      ;;
                C)      ;;
                esac
                ;;

        submenu3)
                case "$X" in
                A)      ;;
                B)      ;;
                C)      ;;
                esac
                ;;

        *)      echo "Unknown menu $1, removing"
                shift
                ;;
        esac
done

echo "Menu list is empty, quitting" >&2
1 Like

Sorry.. I should have been more specific... My OS is solaris.. Shell is bash... Everything in my script is a function.. The script itself is over 2000 lines with every "do something" in the code a function...

The submenus are exacly like the main menu that you see except it will only have the menu design and will willl read an input from the user. The processing of the read is done in the submenu.. for example:

function submenu {
echo "--------------- SUB MENU -------------------"
echo " 1. SUB CH1 "
echo " 2. SUB CH2 "
echo " 3. SUB CH3 "
echo " 4. SUB CH4 "
echo " 5. SUB CH5 "
echo " 6. SUB CH6 "
echo "Choose 1-6"
read subchoice
}
 
function submenu2 {
echo "--------------- SUB MENU 2 -------------------"
echo " 1. SUB2 CH1 "
echo " 2. SUB2 CH2 "
echo " 3. SUB2 CH3 "
echo " 4. SUB2 CH4 "
echo " 5. SUB2 CH5 "
echo " 6. SUB2 CH6 "
echo "Choose 1-6"
read subchoice2
}
 
function submenu3 {
echo "--------------- SUB MENU 3-------------------"
echo " 1. SUB3 CH1 "
echo " 2. SUB3 CH2 "
echo " 3. SUB3 CH3 "
echo " 4. SUB3 CH4 "
echo " 5. SUB3 CH5 "
echo " 6. SUB3 CH6 "
echo "Choose 1-6"
read subchoice3
}

P.S: I have modified my original post to show that the main menu is a function. I have missed it and it must have been very confusing.. Sorry

I think we crossposted. Have a look at my general-purpose bash menu system there :slight_smile:

Thanks a mil...That looks very interesting.. I will try it as soon as i have access to a terminal.

The only thing is, the number is options in my menus are usually 10 or more and the option is uaually something like "Show logs that belong to the XX service" and such. I have never used select before as well so i am a bit apprehensive. I am excited to try this out at the same time

---------- Post updated at 08:19 PM ---------- Previous update was at 08:10 PM ----------

That is exactly how i have it and it will work fine as long as you do not have another submenu "sub_menu_2" inside inside the function "sub_menu"... In this case, when you call the function "sub_menu_2" and then return back to "sub_menu", any choice you make will take you to "sub_menu_2"... This is because, "sub_menu_2" calls "sub_menu" and when you choose an option , it simply returns back after the call and still in the while loop in "sub_menu_2"

I used IFS="|" so you could have long option names with spaces in there.

You'll probably want to check REPLY instead of X for the value select reads, though! That'll be the number the user inputs, rather than the title displayed.

It may even be possible to have the menus read from file...

1 Like

I tried the exact code that you posted and it works a charm. But if i modify it as highlighted above, then i am getting the following error.

In addition, if i define the menus your way, they aren't aesthetically goodlooking which is a requirement for the script. I designed the menu function using tput so that the menus will be properly spaced and will be in the centre of the terminal. I tried calling the tput function to align the menu using backticks as below and it got messy to say the least

i=1
j=4
main) OPTIONS="`align $i $j`;Do maintenance|`align $i $j`;`align $i $j`;Look at logs|`align $i $j`;Look at Processes|Q" ;;
# The align function has a bunch of tput commands and increments the value of i and j for each call of the function.

Well, what's line 58?

The menus look fine to me, but I suppose that's subjective.

The menus are efficient and the code is brilliant.. It eliminates 100's of lines..
As for line 58, its the following line

Do maintenance)    set -- submenu1 "$*"    ;;
"Do Maintenance")

Why are you still selecting based on $X though? Use $REPLY, which will be the number typed in, so you can have cases 1) ;; 2) ;; instead of cramming entire titles into them, padding and all...

1 Like