BASH : got to the upper imbricated case - bash

It is my first question, so I hope that I'm clear enough
I have the following problem with a case inside a case.
the first one is a menu which trigger a function. inside the function there a an other case. In case user select NO, he should come back to the first case.
I have not found how to do that beside of launching the first script ( containing the first case:
menu.sh:
source ./functions.sh
#read answer
while read answer;do
case $answer in
1)
function1
;;
2)
function2
;;
3)
# the same, without function
read -p "do you want to continue [y/n] ?" choice
case $choice in
y|Y)
# DO SOME STUFF
menu.sh
;;
n|N )
./menu.sh
;;
*)
echo "invalid input"
./menu.sh
;;
esac
4)
function4
;;
5)
quit
;;
esac
done
functions.sh:
function1() {
read -p "do you want to continue [y/n] ?" choice
case $choice in
y|Y)
# DO SOME STUFF and go back to menu
echo "hello"
menu.sh
;;
n|N )
./menu.sh
;;
*)
echo "invalid input"
./menu.sh
;;
esac
}
so as you can see, when the function is launched, it launched the second case where the user is asked if he wants to continue. If he choose no, he can go back the menu. But with the way i m doing, i m spawning a menu.sh inside the case.
What is the better solution?
Thank you in advance for your help

As your code layed out, you don't need to run anything to go back to the menu. Just delete the lines in which you are spawning the menu.sh.
An example function1 would look like:
function1() {
read -p "do you want to continue [y/n] ?" choice
case $choice in
y|Y)
# DO SOME STUFF and go back to menu
echo "hello"
;;
n|N )
;;
*)
echo "invalid input"
;;
esac
}
And change
while read answer;do
To
while read -p "Enter a number: " answer;do
It will make it easier to understand how things are working.

Related

Bash menu script with possibility to run with arguments

I have a bash script:
PS3='Please enter your choice: '
options=("1" "2" "3" "4" "Quit")
select opt in "${options[#]}"
do
case $opt in
"1")
echo "Set configuration"
break
;;
"2")
echo "Setting configuration and execution Install"
break
;;
"3")
echo "Setting configuration and execution Unlink"
break
;;
"4")
echo "Running tests"
break
;;
"Quit")
break
;;
*) echo "Selected option '$REPLY' couldn't be find in the list of options";;
esac
done
I have 2 questions:
How can I run this script with predefined option? (For example, I want to execute this script with already selected 1st option)
Is it possible to reuse one option in another option? (For example my 1st option just setting config and my 2nd option also setting the same config and after that execute install, can they be written like if option 2 selected execute 1st option and then 2nd?)
And if something written too badly, I'm open to suggestions =)
How can I run this script with predefined option? (For example, I want
to execute this script with already selected 1st option)
It's a bit ugly with select, move all case logic out from do ... done cycle, make your script take args and rearrange it like this:
#!/bin/bash
PS3='Please enter your choice: '
options=("1" "2" "3" "4" "Quit")
[[ $1 ]] && opt=$1 || select opt in "${options[#]}"; do break; done
case $opt in
"1") echo "Set configuration";;
"2") echo "Setting configuration and execution Install";;
"3") echo "Setting configuration and execution Unlink";;
"4") echo "Running tests";;
"Quit") :;;
*) echo "Selected option '$REPLY' couldn't be find in the list of options";;
esac
Is it possible to reuse one option in another option? (For example my
1st option just setting config and my 2nd option also setting the same
config and after that execute install, can they be written like if
option 2 selected execute 1st option and then 2nd?)
Turn the code in options into functions, this way you could easily reuse it
fun1(){ echo "Set configuration"; }
fun2(){ echo "Execution Install"; }
...
case $opt in
"1") fun1;;
"2") fun1; fun2;;
...
Also there are these operators for case: ;& and ;;&
man bash
...
Using ;& in place of ;; causes execution to continue with the list associated with the next set of patterns.
Using ;;& in place of ;; causes the shell
to test the next pattern list in the statement, if any, and execute any associated list on a successful match.
So if you want to make option 1 also run if option 2 selected this can be done like so:
case $opt in
"2") fun1;&
"1") fun1;;
...
But personally I found this method a bit tricky and hard to read.
If you put the select part in a function
main(){
select opt in "${options[#]}"
do
case $opt in
"1")
set_config # <--- an other funtion for option 1 to reuse it
break
;;
.
.
.
}
# set a default option
def_opt=1
# or use command line argument
def_opt="$1"
you can call main with predefined option '$def_opt' with yes
yes "$def_opt" | main
After digging into this and trying to do my best, I still need a little help to finish my script.
Running script without any parameters are now working perfect.
Passing options in that way (getopts :c:i:u:d:s:h:) leads me to an error message after executing command sh ./script.sh -c => Wrong argument 'c' provided, run sh ./scripts/collection.sh -h for help
Passing options in that way (getopts "ciudsh") => working perfect, but still if I use argument that wasn't passed (ex. x) it would lead to error: Wrong argument '' provided, run sh ./scripts/collection.sh -h for help or sometimes even to this Syntax error: "(" unexpected (expecting "fi")
Please see my full script below, unfortunately for security reasons I can't post the content of the functions itself.
I would appreciate any help on fixing style, errors or anything else.
Based on your advice and other answers on stackoverflow I came up to this:
#!/usr/bin/env bash
#Colors
BRed='\033[1;31m'
Green='\033[0;32m'
BCyan='\033[1;36m'
NC='\033[0m'
f1(){
...
}
f2(){
...
}
f3(){
...
}
f4(){
...
}
f5(){
...
}
Help(){
echo -e "${Green}====================================================================================================================${NC}"
echo "You may execute the commands by selecting a number from menu or pass it as argument, see examples below:"
echo ""
echo -e "${Green}sh $0 ${BCyan}-argument${NC} :To execute specific command"
echo -e "${Green}sh $0 ${NC} :To see menu with all available options"
echo ""
echo -e "${BCyan} -c ${NC}..."
echo -e "${BCyan} -i ${NC}..."
echo -e "${BCyan} -u ${NC}..."
echo -e "${BCyan} -d ${NC}..."
echo -e "${BCyan} -s ${NC}..."
echo -e "${BCyan} -h ${NC}..."
echo -e "${Green}====================================================================================================================${NC}"
exit 1;
}
if [ $# -eq 0 ]
then
PS3='Please enter your choice: '
options=("1" "2" "3" "4" "5" "Help" "Quit")
select opt in "${options[#]}"
do
case $opt in
"1")
f1;;
"2")
f1; f2;;
"3")
f1; f2;;
"4")
f3;;
"5")
f4;;
"Help")
Help;;
"Quit")
break;;
*) echo -e "${BRed}Selected option ${BCyan}'$REPLY'${NC} ${BRed}couldn't be find in the list of provided options${NC}"
break;;
esac
done
fi
while getopts :c:i:u:d:s:h: OPTION
do
case $OPTION in
c)
f1;;
i)
f1; f2;;
u)
f1; f3;;
d)
f4;;
s)
f5;;
h)
Help;;
*) echo -e "${BRed}Wrong argument ${BCyan}'$OPTARG'${NC} ${BRed}provided, run${NC} ${BCyan}sh $0 -h${NC} ${BRed}for help${NC}"
esac
done

I want to take in using the user's input, then call a function using a case statement

I'm working within bash and I'm struggling to take the user's input then move them into a function. I'm using a case statement to get it to work and this is the code I'm currently working with:
read -n1 -p "Save to log? [y,n] " choice \n
case $choice in
y|Y) echo yes ;;
n|N) echo no ;;
*) echo dont know ;;
esac
function saveToLog(){
echo Saving to log
}
And i've tried what I thought would work here:
read -n1 -p "Save to log? [y,n] " choice \n
case $choice in
y|Y) saveToLog() ;;
n|N) echo no ;;
*) echo dont know ;;
esac
function saveToLog(){
echo Saving to log
}
But that doesn't work, I've done some googling and I can't find a solution using a case statement as I'd like to keep using this system.
Thank you. :)
There's no need for the () behind saveToLog:
#!/bin/bash
read -n1 -p "Save to log? [y,n] " choice \n
case $choice in
y|Y) saveToLog ;;
n|N) echo "no" ;;
*) echo "dont know" ;;
esac
function saveToLog(){
echo "Saving to log"
}
Will call saveToLog on y|Y
Note: saveToLog must be delared as it is (using the function keyword) for this to work. Please take a look at this Stackoverflow question for more information about function and ()

Bash Menu - Return to Main Menu from SubMenu?

I've got a simple Bash Menu here. Everything seems to work great, except I can't figure out how to get back to the Main Menu from my SubMenu. Upon executing the script, I select "3" to go into the "SubMenu" from there, I press "3" again to go back to the "Main Menu" but instead it just keeps showing me the "SubMenu" options.
I tried replacing "break" with "./menu.sh" which is the name of my script, which does seem to work, however, I'm certain that's probably not the most ideal way to handle this issue.
#!/bin/bash
clear
while true
do
clear
echo "######"
echo " Menu"
echo "######"
echo ""
PS3='Select an option: '
options=("Option1" "Option2" "SubMenu" "Exit")
select opt in "${options[#]}"
do
case $opt in
"Option1")
echo Option1
read -p ""
break
;;
"Option2")
echo Option2
read -p ""
break
;;
"SubMenu")
while true
do
clear
echo "#########"
echo " SubMenu"
echo "#########"
echo ""
PS3='Select an option: '
options=(
"SubMenu Option1"
"SubMenu Option2"
"Main Menu"
)
select opt2 in "${options[#]}"
do
case $opt2 in
"SubMenu Option1")
echo "Sub-Menu Option1"
read -p ""
break
;;
"SubMenu Option2")
echo "Sub-Menu Option2"
read -p ""
break
;;
"Main Menu")
"./menu"
;;
*) echo "invalid option";;
esac
done
done
;;
"Exit")
exit
;;
*) echo "invalid option"
;;
esac
done
done
First of all, break will break out of select. To leave the submenu while loop, you need to break 3 (to leave the inner select, the submenu while, and the outer select).
Type help break for more information.
A short note on your other approach, calling your script again: You should not launch a new child process with each iteration. Bash has an elegant way of "restarting" a program:
exec "$0"
This will execute the given executable (in this case $0 – the current script) in the current process. See the exec() system call for more information or type help exec to read the Bash specifics.

Bash Scripting: Can you make a function call itself?

I'm making a bash script with a simple interactive menu that asks a yes or no question. I'm wondering if I can use a function to call itself and restart the prompt if the person writes random junk, is this possible?
Code:
Question () {
read -r -p "yes or no quesiotn [Y/N]"
Response
case Response in
Y|y)
#some code
;;
N|n)
#more code
;;
*)
ehco "im sorry i didnt catch that"
Question
;;
esac
}
Question
Instead of calling itself, make the function return appropriate exit codes, (a 1 for failed, a 0 for success), then have a do-nothing until loop do the work:
Question () {
read -r -p "Yes or no question [Y/N]?" Response
case "$Response" in
Y|y)
#some code
;;
N|n)
#more code
;;
*)
echo "I'm sorry I didn't catch that."
return 1
;;
esac
}
until Question ; do : ; done
While you can use recursion in this case, it's not recommended because you'll consume memory (to track the function calls) for each bad response. Instead, just use a loop.
while true; do
read -r -p "yes or no question [Y/N] " response
case "$response" in
Y|y)
# some code
;;
N|n)
# more code
;;
*) echo "please provide a valid response"
continue ;;
esac
break
done

bash case statement with unmatched patterns

Ok, so i have tried searching for this on google and i cant seem to find an answer. What i'm trying to do is create a case statement in bash but if the user enters a different number than listed it just exits the script. How do i make it give an error and then ask for the user to select one of the options?
for example, my case statement
case $ans in
1) echo "Running Project 1..."
sleep 2
./project1.sh
;;
2) echo "Running Project 2..."
sleep 2
./project2.sh
;;
Qq) echo "Exiting"
exit
;;
esac
so any options other than 1, 2, Qq it will give an error saying invalid selection, try again.
You need a while loop and a boolean variable like that:
flag = true
while [ $flag ]; do
case $ans in
1) echo "Running Project 1..."
sleep 2
./project1.sh
;;
2) echo "Running Project 2..."
sleep 2
./project2.sh
;;
Qq) echo "Exiting"
flag = false
;;
esac
done

Resources