Bash script, case statement and sub-menus - bash

I wish to run a script with a case statement leading to choices between other lists of choices (sort of sub-menus) between scripts :
#!/bin/bash
echo "Your choice ?"
echo "1) Foo"
echo "2) Bar"
echo "3) Stuff"
read case;
case $case in
#and this is where I would like to allow
#(here a simplified example I do not manage to code)
1) What script ? # may I use another case statement ?
a) Foo1;;
b) Foo2;;
2) What script ? # d°
a) Bar1;;
b) Bar2;;
c) Bar3;;
esac
Foo1, Foo2, Bar1, Bar2 and Bar3 being in fact bash scripts, I would like to call, eg, sh Foo1 from within the script.
How must I proceed :
May I include a case statement within a case statement (better than if statement, if the choices are numerous) ?
And how do I call a script from within another script ?
Thanks in advance for your help
ThG

Dennis' answer should do what you want. Just to illustrate how to do it with functions, so as to make the script more readable:
function which_foo {
local foo_options=("foo1" "foo2" "cancel")
local PS3="Select foo: "
select foo in "${foo_options[#]}"
do
case $REPLY in
1) ./foo1.sh
break ;;
2) ./foo2.sh
break ;;
3) break ;;
esac
done
}
ACTIONS=("foo" "bar" "quit")
PS3="Select action: "
select action in "${ACTIONS[#]}"
do
case $action in
"foo") which_foo
break ;;
"bar") which_bar # Not present in this example
break ;;
"quit") break ;;
esac
done
As an aside, note:
the use of $REPLY in the case inside which_foo, which is automatically set to the number selected by the user, instead of the text of the option.
the use of break in each case options. Without them, select puts you in a loop, so the user would be asked to enter a choice again.
that select never changes the value of PS3 for you, so you have to do it yourself when switching between the different levels of your menu.

Yes, you can nest case statements. You should also consider using a select statement instead of all those echo and read statements.
options=("Option 1" "Option 2" "Option3" "Quit")
optionsprompt='Please enter your choice: '
sub1=("Option 1 sub 1" "Option 1 sub 2")
sub1prompt='Please enter your choice: '
PS3=$optionsprompt
select opt in "${options[#]}"
do
case $opt in
"Option 1")
echo "you chose choice 1"
PS3=$sub1prompt
select sub1opt in "${sub1[#]}"
do
case $sub1opt in
"Option 1 sub 1")
echo "you chose choice 2"
;;
"Option 1 sub 2")
echo "you chose choice 2"
;;
esac
done
;;
"Option 2")
echo "you chose choice 2"
;;
"Option 2")
echo "you chose choice 3"
;;
"Quit")
break
;;
*) echo invalid option;;
esac
done
The first level menu would look like this:
1) Option 1
2) Option 2
3) Option3
4) Quit
Please enter your choice:
However, that can get very hairy very quickly. It would be better to break it up into functions where the inner select/case statements are in separate functions or are abstracted so you can pass arguments to control them.
To call another script, you might want to set a variable that holds the directory. It would look something like this:
basedir=/some/path
case ...
...
$basedir/Foo # call the script

Related

Bash handle multiple user menu choices with same variables

I currently have script which handles user choice based on presented menu and depending on what option is selected, variables are assigned values and some variables are assigned values based on if conditions. Then some functions are called using that variables. Simplified logic is here
PS3='Please make your choice'
options=("Option 1" "Option 2" "Quit")
COLUMNS=0
select opt in "${options[#]}"
do
case $opt in
"Option 1")
varA="Value1"
varB="Text1"
if something; then
varC="String1"
else
varC="String2"
fi
break
;;
"Option 2")
varA="Value2"
varB="Text2"
break
;;
"Quit")
exit 0
;;
*)
echo $red "Invalid option $REPLY" $txtreset;;
esac
prepare "${varA}" "${varB}"
update "${varA}" "${varB}" "${varC}"
So this logic allows one user choice and then processing happens based on that choice.
Is it possible to capture multiple users choices, like Option 1 and Option 2 for example and then run further script steps in a loop twice. Once using variable values from 1st choice and second time using variable values from 2nd choice.?

Getting a menu to loop when invalid choice is made BASH

Hey guys so I am trying to get this menu to loop whenever an invalid choice is made in a case statement but having a hard time figuring out what I should be calling back to in my while loop I tried using * as that is whats referenced in case as the invalid choice but it expects an operand when it sees that so I am not sure how to call it back below is the code any help is greatly appreciated.
#Main menu.
#Displays a greeting and waits 8 seconds before clearing the screen
echo "Hello and welcome to the group 97 project we hope you enjoy using our program!"
sleep 8s
clear
while [[ $option -eq "*" ]]
do
#Displays a list of options for the user to choose.
echo "Please select one of the folowing options."
echo -e "\t0. Exit program"
echo -e "\t1. Find the even multiples of any number."
echo -e "\t2. Find the terms of any linear sequence given the rule Un=an+b."
echo -e "\t2. Find the numbers that can be expressed as the product of two nonnegative integers in succession and print them in increasing order."
#Reads the option selection from user and checks it against case for what to do.
read -n 1 option
case $option in
0)
exit ;;
1)
echo task1 ;;
2)
echo task2 ;;
3)
echo task3 ;;
*)
clear
echo "Invalid selection, please try again.";;
esac
done
A select implementation of your menu:
#!/usr/bin/env bash
PS3='Please select one of the options: '
select _ in \
'Exit program' \
'Find the even multiples of any number.' \
'Find the terms of any linear sequence given the rule Un=an+b.' \
'Find the numbers that can be expressed as the product of two nonnegative integers in succession and print them in increasing order.'
do
case $REPLY in
1) exit ;;
2) echo task1 ;;
3) echo task2 ;;
4) echo task3 ;;
*) echo 'Invalid selection, please try again.' ;;
esac
done
Don't reinvent the builtin select
command
choices=(
"Exit program"
"Find the even multiples of any number."
"Find the terms of any linear sequence given the rule Un=an+b."
"Find the numbers that can be expressed as the product of two nonnegative integers in succession and print them in increasing order."
)
PS3="Please select one of the options: "
select choice in "${choices[#]}"; do
case $choice in
"${choices[0]}") exit ;;
"${choices[1]}")
echo task1
break ;;
"${choices[2]}")
echo task2
break ;;
"${choices[3]}")
echo task3
break ;;
esac
done
If you want to stay in the menu until "Exit" then remove the breaks.

Bash - Iterating over select menu options after choosing an option?

I am reading up on Select menus in Bash:
Making menus with the select built-in
I am quite confused when it comes to iterating over the options in the menu and would appreciate any feedback on this.
Using the code here as a starting point:
How can I create a select menu in a shell script?
We get the following output:
root#dev:~/# ./test.sh
1) Option 1
2) Option 2
3) Option 3
4) Quit
Please enter your choice: 1
you chose choice 1
Please enter your choice: 2
you chose choice 2
Please enter your choice: 4
root#dev:~/#
What I would like to try to do, is show the select options again once an option has been chosen, so the output would be like:
root#dev:~/# ./example.sh
1) Option 1
2) Option 2
3) Quit
Please enter your choice: 1
you chose choice 1
1) Option 1
2) Option 2
3) Quit
Please enter your choice: 2
you chose choice 2
1) Option 1
2) Option 2
3) Quit
Please enter your choice: 3
root#dev:~/#
So I have given this a Bash (L) and and tried to loop through the options (array?):
#!/bin/bash
PS3='Please enter your choice: '
options=("Option 1" "Quit")
select opt in "${options[#]}"
do
case $opt in
"Option 1")
echo "you chose choice 1"
# Create an incrementing value
loop=1;
# Loop through each option
for option in ${options[#]} ; do
# Echo the option
echo "$loop) $option";
# Increment the loop
loop=$((loop+1));
done
;;
"Quit")
break
;;
*) echo invalid option;;
esac
done
But then I get an output like:
root#dev:~/# ./stumped.sh
1) Option 1
2) Quit
Please enter your choice: 1
you chose choice 1
1) Option
2) 1
3) Quit
Please enter your choice:
So it seems that the array values are separated by spaces here?
To my understanding: options=("Option 1" "Quit") is creating an array of 2 values and not of 3 however it is being interpreted in Bash as 3 and I am not sure why.
Could someone enlighten me and explain why this is happening?
Let's create a function that shows the menu and echoes the user's choice:
function show_menu {
select option in "${OPTIONS[#]}"; do
echo $option
break
done
}
Now, we can wrap that function in a loop, and only break out if the user picked Quit:
while true; do
option=$(show_menu)
if [[ $option == "Quit" ]]; then
break
fi
done
Voila!
When you want the numbered options shown each iteration, let select do that for you.
options=("Option one" "two" three "and number four" quit)
while true; do
select item in "${options[#]}" ; do
if [ -n "${item}" ]; then
break
fi
echo "Sorry, please enter a number as shown."
break
done;
if [[ "${item}" = "quit" ]]; then
break
fi
if [ -n "${item}" ]; then
echo "Your answer is ${item}"
fi
done

How to use goto statement in shell script [duplicate]

This question already has answers here:
Is there a "goto" statement in bash?
(14 answers)
Closed 4 years ago.
I am beginner in shell script. I don't have any idea about how to use goto statement. I am using the following code.
start:
echo "Main Menu"
echo "1 for Copy"
echo "2 for exit"
read NUM
case $NUM in
"1")
echo "CopyNUM"
goto start:;
"2")
echo "Haiiii";
goto start:
*)
echo "ssss";
esac
As others have noted, there's no goto in bash (or other POSIX-like shells) - other, more flexible flow-control constructs take its place.
Look for heading Compound Commands in man bash.
In your case, the select command is the right choice.
Since how to use it may not be obvious, here's something to get you started:
#!/usr/bin/env bash
echo "Main Menu"
# Define the choices to present to the user, which will be
# presented line by line, prefixed by a sequential number
# (E.g., '1) copy', ...)
choices=( 'copy' 'exit' )
# Present the choices.
# The user chooses by entering the *number* before the desired choice.
select choice in "${choices[#]}"; do
# If an invalid number was chosen, $choice will be empty.
# Report an error and prompt again.
[[ -n $choice ]] || { echo "Invalid choice." >&2; continue; }
# Examine the choice.
# Note that it is the choice string itself, not its number
# that is reported in $choice.
case $choice in
copy)
echo "Copying..."
# Set flag here, or call function, ...
;;
exit)
echo "Exiting. "
exit 0
esac
# Getting here means that a valid choice was made,
# so break out of the select statement and continue below,
# if desired.
# Note that without an explicit break (or exit) statement,
# bash will continue to prompt.
break
done
Here is a short example using a select loop to accomplish your goal. You can use a while loop with a custom menu if you want custom formatting, but the basic menu is what select was designed to do:
#!/bin/bash
## array of menu entries
entries=( "for Copy"
"for exit" )
## set prompt for select menu
PS3='Selection: '
while [ "$menu" != 1 ]; do ## outer loop redraws menu each time
printf "\nMain Menu:\n\n" ## heading for menu
select choice in "${entries[#]}"; do ## select displays choices in array
case "$choice" in ## case responds to choice
"for Copy" )
echo "CopyNUM"
break ## break returns control to outer loop
;;
"for exit" )
echo "Haiiii, exiting"
menu=1 ## variable setting exit condition
break
;;
* )
echo "ssss"
break
;;
esac
done
done
exit 0
Use/Output
$ bash select_menu.sh
Main Menu:
1) for Copy
2) for exit
Selection: 1
CopyNUM
Main Menu:
1) for Copy
2) for exit
Selection: 3
ssss
Main Menu:
1) for Copy
2) for exit
Selection: 2
Haiiii, exiting

Exit a bash switch statement

I've written a menu driven bash script that uses a switch case inside of a while loop to perform the various menu options. Everything works just fine. Now I'm trying to improve the program by performing error testing on the user input, but I cannot seem to make it work...
The problem is I don't know how to properly break out of a switch statement, without breaking out of the while loop (so the user can try again).
# repeat command line indefinitely until user quits
while [ "$done" != "true" ]
do
# display menu options to user
echo "Command Menu" # I cut out the menu options for brevity....
# prompt user to enter command
echo "Please select a letter:"
read option
# switch case for menu commands, accept both upper and lower case
case "$option" in
# sample case statement
a|A) echo "Choose a month"
read monthVal
if [ "$monthVal" -lt 13 ]
then
cal "$monthVal"
else
break # THIS DOES NOT WORK. BREAKS WHILE LOOP, NOT SWITCH!
fi
;;
q|Q) done="true" #ends while loop
;;
*) echo "Invalid option, choose again..."
;;
esac
done
exit 0
The program works fine when the user enters a valid month value, but if they enter a number higher than 13, instead of breaking the switch statement and repeating the loop again, the program breaks both the switch and while loop and stops running.
Hitting ;; will terminate the case statement. Try not doing anything at all:
a|A) echo "Choose a month"
read monthVal
if [ "$monthVal" -lt 13 ]
then
cal "$monthVal"
fi
;;
Move the body of that case into a function, and you can return from the function at will.
do_A_stuff() {
echo "Choose a month"
read monthVal
if [ "$monthVal" -lt 13 ]
then
cal "$monthVal"
else
return
fi
further tests ...
}
Then
case $whatever in
a|A) do_A_stuff ;;
I think what you mean to do with break is "quit this case statement and restart the while loop". However, case ... esac isn't a control-flow statement (although it may smell like one), and doesn't pay attention to break.
Try changing break into continue, which will send control back to the start of the while loop.
This should do the trick: Wrap the code in a one-trip for-loop:
#! /bin/bash
case foo in
bar)
echo "Should never get here."
;;
foo)
for just in this_once ; do
echo "Top half only."
if ! test foo = bar; then break; fi
echo "Bottom half -- should never get here."
done
;;
*)
echo "Should never get here either."
;;
esac
In your example, there is no point in breaking, you can omit the else break statement altogether.
The problem occurs when there is code that runs after the point where you would have broken. You'd want to write something like that
case $v in
a) if [ $x ]; then bla; else break; fi;
some more stuff ;;
b) blablabla ;;
What I usually do (because creating a function is such a hassle with copy pasting, and mostly it breaks the flow of the program when you read it to have a function somewhere else) is to use a break variable (which you can call brake for fun when you have a lame sense of humor like me) and enclose "some more stuff" in an if statement
case $v in
a) if [ $x ]; then bla; else brake="whatever that's not an empty string"; fi;
if [ -z "$brake" ];then some more stuff; brake=""; fi;;
#don't forget to clear brake if you may come back here later.
b) blablabla ;;
esac

Resources