Bash Scripting: Can you make a function call itself? - bash

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

Related

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 prompt with yes, no and cancel

I'm new in Bash and I'm stuck with writing a prompt function which asks the user a given questions and accepts the answers yes, no and cancel. I know that there are already a lot of answers to similar questions here on SO but I wasn't able to find an answer that fulfills all my requirements.
Requirements:
The ask function must
accept a question as parameter: ask "are you happy?"
accept a answer in [y/n/c]: y, Y, yes, yEs, etc.
return a value based on the answer: true for yes, false for no
the return value must be assignable to a variable to save it for later: answer=$(ask "...?")
the return value must be directly useable by if statements: if ask "...?" then;
be able to halt the complete execution of the calling script for abort: exit
ask again if the answer was not yes, no or cancel
allow a default answer for empty input
work in scripts that use set -e as well as set +e
I came up with the following solution which doesn't work properly:
ask() {
while true; do
read -p "$1 [Y/n/a] " answer
case $(echo "$answer" | tr "[A-Z]" "[a-z]") in
y|yes|"" ) echo "true" ; return 0;;
n|no ) echo "false"; return 1;;
a|abort ) echo "abort"; exit 1;;
esac
done
}
# usage1
if ask "Are you happy?" >/dev/null; then
# ...
fi
# usage2
answer=$(ask "Are you happy?")
For example, the abort does only work when I use -e but then logically the no also causes the script to halt.
I believe it would be just overall simpler to work the same way as read works. Remember to pick a unique name for the namereference.
ask() {
declare -n _ask_var=$2
local _ask_answer
while true; do
read -p "$1 [Y/n/a] " _ask_answer
case "${_ask_answer,,}" in
y|yes|"" ) _ask_var="true" ; break; ;;
n|no ) _ask_var="false"; break; ;;
a|abort ) exit 1; ;;
esac
done
}
ask "Are you happy?" answer
if "$answer"; then echo "Yay! Me too!"; fi

Bash Case/Switch Formatting

I'm trying to write a case/switch statement in my bash script as follows:
case "$REPLY" in
E*|e*) $EDITOR "$COMMIT_MSG_FILE" < $TTY; continue ;;
Y*|y*) exit 0 ;;
N*|n*) exit 1 ;;
*) SKIP_DISPLAY_WARNINGS=1; create_prompt; continue ;;
esac
However, I keep getting
syntax error near unexpected token ';;'
E*|e*) $EDITOR "$COMMIT_MSG_FILE" < $TTY; continue ;;'
From reading around, I know that ;; is the equivalent of a break statement in a traditional switch statement, but I'm not sure why I'm getting a syntax error here. All of the functions and variables are defined above, so I can't see that being an issue. Any advice?
EDIT: Entirety of the loop:
while true; do
read_commit_message
check_commit_valid
# if there are no warnings, then the commit is good and we can exit!
test ${#WARNINGS[#]} -eq 0 && exit 0;
# if we're still here, there are warnings we need to display
show_warnings
# if non-interactive don't prompt and exit with an error
# need interactivity for the prompt to show and get response
if [ ! -t 1 ] && [ -z ${FAKE_TTY+x} ]; then
exit 1
fi
# show message asking for proceed, etc
echo -en "${BLUE}Proceed with commit? [e/y/n/?] ${NO_COLOR}"
# read the response
read REPLY < "$TTY"
# Check if the reply is valid
case "$REPLY" in
E*|e*) $EDITOR "$COMMIT_MSG_FILE" < $TTY; continue ;;
Y*|y*) exit 0 ;;
N*|n*) exit 1 ;;
*) SKIP_DISPLAY_WARNINGS=1; create_prompt; continue ;;
esac
done

BASH : got to the upper imbricated case

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.

BASH - getopts not working properly

I'm currently having problems with my script. Basically, what I want to happen is when I execute ./apache_new_vhost.sh -a -d google.com, it will create a file and directories and if I use the -r option, it should delete.
The script was able to use the functions like add_vhost. It could create a configuration and folder however the filename is empty because it could not read the value I passed to $domain.
while getopts ":a:r:d:h" opt; do
case $opt in
a) action=add_vhost
;;
r) action=remove_vhost
;;
d) domain=$OPTARG
;;
h) usage
exit 1
;;
\?) echo "Invalid option: -$OPTARG"
usage
exit 1
;;
:) echo "Error: option -$OPTARG requires an argument."
usage
exit 1
;;
esac
done
#if [ -z $domain ]; then
# usage
# exit 1
if [ $action == "add_vhost" ]; then
echo $action $domain
elif [ $action == "remove_vhost" ]; then
echo $action $domain
fi
The options are processed in the order you specify them on the command line. So in your example, case a) is processed first, and calls your add_vhost function right then.
But the d) case hasn't been processed yet, so you haven't set domain.
You need to change your logic a bit. Rather than calling your functions directly from the case statement, save what action was selected. i.e.:
a) action="add_vhost"
;;
Then after the case, check that you do have an action selected, and call that function.
As per your script you expect argument after option -a. So when you execute your script by
./apache_new_vhost.sh -a -d google.com
then -d will consider as argument given to -a option. So your second argument discarded.To solve it just give any argument after -a (ex: ./apache_new_vhost.sh -a 1 -d google.com )option or make changes in your getopt
while getopts ":ar:d:h" opt; do

Resources