Exit a bash switch statement - bash

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

Related

Is there any better way to repeatedly call function for each case statement

I wrote a bash script to automate lamp installing in my server, they work great but there's a problem regarding its readability.
# Asking if want to install PHP or build from source
read -p "This script will install PHP, do you want to install it (y/n)?" phpchoice
case "$phpchoice" in
[Yy]* ) isinstallphp=yes;;
[Nn]* ) isinstallphp=no;;
* ) echo "invalid";;
esac
if [ isinstallphp == "yes" ]; then
# Prompt for php version
options=("php5.6" "php7.0" "php7.1" "php7.2" "php7.3" "php7.4" "php8.0" "php8.1")
function do_something () {
echo -e "${Ye}You picked ${Wh}$php_opt${Ye}, will be installing that instead${Nc}"
}
select php_opt in "${options[#]}"; do
case "$php_opt,$REPLY" in
php5.6,*|*,php5.6) do_something; break ;;
php7.0,*|*,php7.0) do_something; break ;;
php7.1,*|*,php7.1) do_something; break ;;
php7.2,*|*,php7.2) do_something; break ;;
php7.3,*|*,php7.3) do_something; break ;;
php7.4,*|*,php7.4) do_something; break ;;
php8.0,*|*,php8.0) do_something; break ;;
php8.1,*|*,php8.1) do_something; break ;;
esac
done
fi
The case statement part where I put do_something function looks messy, all the function do is echo which php option user choose in color. Is there any way to shorten the code ?
The case statement part where I put do_something function looks messy, all the function do is echo which php option user choose in color. Is there any way to shorten the code ?
It appears that you are accommodating the dual possibilities that the user chooses an option by typing its number or by typing the item text. One simplification would be to require the user to make their selection by item number only, in which case you would only need to confirm that $php_opt was not set to NULL. The case statement would not be required at all.
If you want to retain the full input functionality of the current script, then you can still do better than the current code. Instead of a long case statement covering all the options, check whether $php_opt is NULL, and if it is, check whether $REPLY is equal to one of the options. There is a variety of ways you could implement that, but I like this one:
validate_option() {
local choice=$1
while [[ $# -gt 1 ]]; do
shift
[[ "$choice" = "$1" ]] && return 0
done
return 1
}
# ...
select php_opt in "${options[#]}"; do
if [[ -n "$php_opt" ]] || validate_option "$REPLY" "${options[#]}"; then
do_something
fi
done
I find that a lot clearer. Note also that the validate_option function is reusable, and that this approach is completely driven by the option list, so you don't need to modify this code if the option list changes.
Addendum
You additionally raised a question about your given do_something function not printing the selected option when the user enters the option value instead of its number. This will have been a behavior of your original code, too. It arises from the fact that the select command sets the specified variable (php_opt in your case) to NULL in the event that the user enters a non-empty response that is not one of the menu item numbers.
If you want to avoid that, and perhaps also to have the selected value in the form of the option string for other, later processing, then you probably want to address that issue in the body of the select statement. Something like this variation might do, for example:
select php_opt in "${options[#]}"; do
if [[ -n "$php_opt" ]] ||
{ php_opt=$REPLY; validate_option "$php_opt" "${options[#]}"; }; then
do_something
fi
done
That copies the user-entered text into $php_opt in the event that the user entered something other than an option number. Do note that that behavior change might have other effects later in the script, too.
What you're checking is if $php_opt or $REPLY is in the $options array:
array_contains() {
local -n ary=$1
local elem=$2
local IFS=$'\034'
[[ "${IFS}${ary[*]}$IFS" == *"${IFS}${elem}$IFS"* ]]
}
#...
select php_opt in "${options[#]}"; do
if array_contains options "$php_opt" || array_contains options "$REPLY"; then
do_something
break
fi
done
local -n requires bash v4.3+
Octal 034 is the ASCII "FS" character, it's unlikely to be in your data
select sets the given variable name to the selected string, only if a valid selection is made. If an invalid selection is made (ie. not one of the available numbers), $php_opt will be empty. Even if a valid selection was made in a previous iteration, it will be reset to empty.
Hence you just need to test if $php_opt is empty or not:
select php_opt in "${options[#]}"; do
# check for literal reply
reply=${REPLY,,}
for i in "${options[#]}"; do
if [[ "$i" == "$reply" ]]; then
php_opt=$reply
break
fi
done
if [[ "$php_opt" ]]; then
do_something
break
else
echo "$REPLY: invalid selection"
fi
done
Or with case:
select php_opt in "${options[#]}"; do
# check for literal reply
reply=${REPLY,,}
for i in "${options[#]}"; do
if [[ "$i" == "$reply" ]]; then
php_opt=$reply
break
fi
done
case $php_opt in
'') echo "$REPLY: invalid selection";;
*) do_something; break
esac
done
From help select:
If the line consists of the number corresponding to one of the displayed words, then NAME is set to that word. If the line is empty, WORDS and the prompt are redisplayed. If EOF is read, the command completes. Any other value read causes NAME to be set to null. The line read is saved in the variable REPLY.
Also note that you are missing $ in your if statement.
edit: as per the comment, I added a check for a literal name, as well as a number. It's case insensitive, PHP7.0 is valid input. Using the original array is easier to maintain, than a second list of patterns in a case statement.
To make things tidier, you can also put do_something after select (so you only need break). Maybe something like [[ "$reply" == [Qq] ]] && exit too. Similarly in your very first case statement, just do exit if the reply is no.

Can I use functions as tests in a Bash case statement?

can I use a function instead of regular patterns, strings etc. in a bash case statement like this?
#!/bin/bash
var="1"
is_one () {
[$var -eq 1]
}
case $var in
is_one)
"var is one"
;;
*)
"var is not one"
break
;;
esac
You can use functions in statements where could also use programs like test/[, that is while, until, and if. The case statement however allows only patterns, no programs/functions.
Either use
if is_one; then
echo "var is one"
else
echo "var is not one"
done
or
case $var in
1) echo "var is one" ;;
*) echo "var is not one" ;;
esac
Of course you can use a function, but the function should print the pattern you want to match as a glob. That's what case operates on.
is_one () {
echo 1
}
case $var in
"$(is_one)") echo 'It matched!';;
*) echo ':-(';;
esac
Perhaps a better design if you really want to use a function is to write a one which (doesn't contain syntax errors and) returns a non-zero exit code on failure. In this particular case, encapsulating it in a function doesn't really make much sense, but of course you can:
is_one () {
[ "$1" -eq 1 ]
}
if is_one "$var"; then
echo 'It matched!"
else
echo ':-('
fi
Notice the non-optional spaces inside [...] and also the quoting, and the use of a parameter instead of hard-coding a global variable.
Furthermore, note that -eq performs numeric comparisons, and will print an unnerving error message if $var is not numeric. Perhaps you would prefer to use [ "$1" = "1" ] which performs a simple string comparison. This is also somewhat closer in semantics to what case does.
The main difference is that if... elif... distinguishes its alternatives based on the exit codes of the commands being executed, while case.... bases it on string values. Your function, when invoked, exhibits its result only via its exit code.
If you want to use your function with a case, you need to turn this exit code into a string, for instance like this:
is_one
var=$? # Store exit code as a string into var
case $var in
1) echo "exit code is one" ;;
101) echo "exit code is onehundredandone" ;;
*) echo "I do not care what the exit code is" ;;
esac

bash: choose default from case when enter is pressed in a "select" prompt

I'm prompting questions in a bash script like this:
optionsAudits=("Yep" "Nope")
echo "Include audits?"
select opt in "${optionsAudits[#]}"; do
case $REPLY in
1) includeAudits=true; break ;;
2) includeAudits=false; break ;;
"\n") echo "You pressed enter"; break ;; # <--- doesn't work
*) echo "What's that?"; exit;;
esac
done
How can I select a default option when enter is pressed? The "\n" case does not catch the enter key.
To complement Aserre's helpful answer, which explains the problem with your code and offers an effective workaround, with background information and a generic, reusable custom select implementation that allows empty input:
Background information
To spell it out explicitly: select itself ignores empty input (just pressing Enter) and simply re-prompts - user code doesn't even get to run in response.
In fact, select uses the empty string to signal to user code that an invalid choice was typed.
That is, if the output variable - $opt, int this case - is empty inside the select statement, the implication is that an invalid choice index was typed by the user.
The output variable receives the chosen option's text - either 'Yep' or 'Nope' in this case - not the index typed by the user.
(By contrast, your code examines $REPLY instead of the output variable, which contains exactly what the user typed, which is the index in case of a valid choice, but may contain extra leading and trailing whitespace).
Note that in the event that you didn't want to allow empty input, you could
simply indicate to the user in the prompt text that ^C (Ctrl+C) can be used to abort the prompt.
Generic custom select function that also accepts empty input
The following function closely emulates what select does while also allowing empty input (just pressing Enter). Note that the function intercepts invalid input, prints a warning, and re-prompts:
# Custom `select` implementation that allows *empty* input.
# Pass the choices as individual arguments.
# Output is the chosen item, or "", if the user just pressed ENTER.
# Example:
# choice=$(selectWithDefault 'one' 'two' 'three')
selectWithDefault() {
local item i=0 numItems=$#
# Print numbered menu items, based on the arguments passed.
for item; do # Short for: for item in "$#"; do
printf '%s\n' "$((++i))) $item"
done >&2 # Print to stderr, as `select` does.
# Prompt the user for the index of the desired item.
while :; do
printf %s "${PS3-#? }" >&2 # Print the prompt string to stderr, as `select` does.
read -r index
# Make sure that the input is either empty or that a valid index was entered.
[[ -z $index ]] && break # empty input
(( index >= 1 && index <= numItems )) 2>/dev/null || { echo "Invalid selection. Please try again." >&2; continue; }
break
done
# Output the selected item, if any.
[[ -n $index ]] && printf %s "${#: index:1}"
}
You could call it as follows:
# Print the prompt message and call the custom select function.
echo "Include audits (default is 'Nope')?"
optionsAudits=('Yep' 'Nope')
opt=$(selectWithDefault "${optionsAudits[#]}")
# Process the selected item.
case $opt in
'Yep') includeAudits=true; ;;
''|'Nope') includeAudits=false; ;; # $opt is '' if the user just pressed ENTER
esac
Alternative implementation that lets the function itself handle the default logic:Thanks, RL-S
This implementation differs from the above in two respects:
It allows you to designate a default among the choices, by prefixing it with !, with the first choice becoming the default otherwise. The default choice is printed with a trailing [default] (and without the leading !). The function then translates empty input into the default choice.
The selected choice is returned as a 1-based index rather than the text. In other words: you can assume that a valid choice was made when the function returns, and that choice is indicated by its position among the choices given.
# Custom `select` implementation with support for a default choice
# that the user can make by pressing just ENTER.
# Pass the choices as individual arguments; e.g. `selectWithDefault Yes No``
# The first choice is the default choice, unless you designate
# one of the choices as the default with a leading '!', e.g.
# `selectWithDefault Yes !No`
# The default choice is printed with a trailing ' [default]'
# Output is the 1-based *index* of the selected choice, as shown
# in the UI.
# Example:
# choice=$(selectWithDefault 'Yes|No|!Abort' )
selectWithDefault() {
local item i=0 numItems=$# defaultIndex=0
# Print numbered menu items, based on the arguments passed.
for item; do # Short for: for item in "$#"; do
[[ "$item" == !* ]] && defaultIndex=$(( $i + 1)) && item="${item:1} [default]"
printf '%s\n' "$((++i))) $item"
done >&2 # Print to stderr, as `select` does.
# Prompt the user for the index of the desired item.
while :; do
printf %s "${PS3-#? }" >&2 # Print the prompt string to stderr, as `select` does.
read -r index
# Make sure that the input is either empty or that a valid index was entered.
[[ -z $index ]] && index=$defaultIndex && break # empty input == default choice
(( index >= 1 && index <= numItems )) 2>/dev/null || { echo "Invalid selection. Please try again." >&2; continue; }
break
done
# Output the selected *index* (1-based).
printf $index
}
Sample call:
# Print the prompt message and call the custom select function,
# designating 'Abort' as the default choice.
echo "Include audits?"
ndx=$(selectWithDefault 'Yes' 'No', '!Abort')
case $ndx in
1) echo "include";;
2) echo "don't include";;
3) echo "abort";;
esac
Optional reading: A more idiomatic version of your original code
Note: This code doesn't solve the problem, but shows more idiomatic use of the select statement; unlike the original code, this code re-displays the prompt if an invalid choice was made:
optionsAudits=("Yep" "Nope")
echo "Include audits (^C to abort)?"
select opt in "${optionsAudits[#]}"; do
# $opt being empty signals invalid input.
[[ -n $opt ]] || { echo "What's that? Please try again." >&2; continue; }
break # a valid choice was made, exit the prompt.
done
case $opt in # $opt now contains the *text* of the chosen option
'Yep')
includeAudits=true
;;
'Nope') # could be just `*` in this case.
includeAudits=false
;;
esac
Note:
The case statement was moved out of the select statement, because the latter now guarantees that only valid inputs can be made.
The case statement tests the output variable ($opt) rather than the raw user input ($REPLY), and that variable contains the choice text, not its index.
Your problem is due to the fact that select will ignore empty input. For your case, read will be more suitable, but you will lose the utility select provides for automated menu creation.
To emulate the behaviour of select, you could do something like that :
#!/bin/bash
optionsAudits=("Yep" "Nope")
while : #infinite loop. be sure to break out of it when a valid choice is made
do
i=1
echo "Include Audits?"
#we recreate manually the menu here
for o in "${optionsAudits[#]}"; do
echo "$i) $o"
let i++
done
read reply
#the user can either type the option number or copy the option text
case $reply in
"1"|"${optionsAudits[0]}") includeAudits=true; break;;
"2"|"${optionsAudits[1]}") includeAudits=false; break;;
"") echo "empty"; break;;
*) echo "Invalid choice. Please choose an existing option number.";;
esac
done
echo "choice : \"$reply\""
Updated answer:
echo "Include audits? 1) Yep, 2) Nope"
read ans
case $ans in
Yep|1 ) echo "yes"; includeAudits=true; v=1 ;;
Nope|2 ) echo "no"; includeAudits=false; v=2 ;;
"" ) echo "default - yes"; includeAudits=true; v=1 ;;
* ) echo "Whats that?"; exit ;;
esac
This accepts either "Yep" or "1" or "enter" to select yes, and accepts "Nope" or "2" for no, and throws away anything else. It also sets v to 1 or 2 depending on whether the user wanted yes or no.
This will do what you are asking for.
options=("option 1" "option 2");
while :
do
echo "Select your option:"
i=1;
for opt in "${options[#]}"; do
echo "$i) $opt";
let i++;
done
read reply
case $reply in
"1"|"${options[0]}"|"")
doSomething1();
break;;
"2"|"${options[1]}")
doSomething2();
break;;
*)
echo "Invalid choice. Please choose 1 or 2";;
esac
done
Assuming that your default option is Yep:
#!/bin/bash
optionsAudits=("Yep" "Nope")
while : #infinite loop. be sure to break out of it when a valid choice is made
do
i=1
echo "Include Audits?: "
#we recreate manually the menu here
for o in "${optionsAudits[#]}"; do
echo " $i) $o"
let i++
done
read -rp "Audit option: " -iYep
#the user can either type the option number or copy the option text
case $REPLY in
"1"|"${optionsAudits[0]}") includeAudits=true; break;;
"2"|"${optionsAudits[1]}") includeAudits=false; break;;
"") includeAudits=true; break;;
*) echo "Invalid choice. Please choose an existing option number.";;
esac
done
echo "choice : \"$REPLY\""
echo "includeAudits : \"$includeAudits\""
Noticed the line:
read -rp "Audit option: " -eiYep
Also I pulled up $reply to $REPLY so that the case decision works better.
The output would now look like this upon hitting ENTER:
Include Audits?:
1) Yep
2) Nope
Audit option: Yep
choice : ""
includeAudits : "true"
#
As an enhancement over select, read -eiYep will supply Yep default value into the input buffer up front.
Only downside of fronting the default value is that one would have to press backspace a few times to enter in their own answer.

Bash case statement restart on invalid input

Trying to figure out a way to return in a case statement. For example, if you run this....
while :
do
clear
cat<<-EOF
======================
Foo Bar Doo Dah Setup
======================
(1) Foo
(2) Bar
(q) Quit
----------------------
EOF
read
case $REPLY in
"1") foo="foo" ;;
"2") bar="bar" ;;
"q") break ;;
* ) echo "Invalid Option"
esac
sleep .5
clear
cat<<-EOF
======================
Foo Bar Doo Dah Setup
======================
(1) Doo
(2) Dah
(q) Quit
----------------------
EOF
read
case $REPLY in
"1") doo="doo" ;;
"2") dah="dah" ;;
"q") break ;;
* ) echo "Invalid Option"
esac
sleep .5
done
...and enter an "Invalid Option" then you'll notice it moves on to the next case instead of re-evaluating the case.
The workaround isn't too bad just have to nest the case statement into an if statement within a while loop ...
while read; do
if [ $REPLY -ge 1 -a $REPLY -le 2 ]; then
case $REPLY in
"1") foo="foo" ;;
"2") bar="bar" ;;
esac
break
elif [ $REPLY == q ]; then
break
else
echo "Invalid Option"
fi
done
That being said seems a bit much, anyone know of some form a loop control to rerun a case statement from a case selection?
If you need a valid choice from the menu before continuing to the next menu, then you need a loop around the choosing process. You should probably also count the failures and terminate after some number of consecutive failures such as 10.
The shell loop constructs support both break and continue, and these can optionally be followed by a number indicating how many loop levels should be broken (the default number of levels is 1).
The code should also heed EOF detected by read and terminate the loops. That's achieved with the answer variable in the code below.
This leads to code like:
retries=0
max_retries=10
while [ $retries -lt $max_retries ]
do
clear
cat <<-'EOF'
======================
Foo Bar Doo Dah Setup
======================
(1) Foo
(2) Bar
(q) Quit
----------------------
EOF
retries=0
answer=no
while read
do
case "$REPLY" in
"1") foo="foo"; answer=yes; break;;
"2") bar="bar"; answer=yes; break;;
"q") break 2;; # Or exit, or return if the code is in a function
* ) echo "Invalid Option ('$REPLY' given)" >&2
if [ $((++retries)) -ge $max_retries ]; then break 2; fi
;;
esac
done
if [ "$answer" = "no" ]; then break; fi # EOF in read loop
sleep .5
clear
cat <<-'EOF'
======================
Foo Bar Doo Dah Setup
======================
(1) Doo
(2) Dah
(q) Quit
----------------------
EOF
retries=0
answer=no
while read
do
case $REPLY in
"1") doo="doo"; answer=yes;;
"2") dah="dah"; answer=yes;;
"q") break 2;;
* ) echo "Invalid Option ('$REPLY' given)"" >&2
if [ $((++retries)) -ge $max_retries ]; then break 2; fi
;;
esac
done
if [ "$answer" = "no" ]; then break; fi # EOF in read loop
sleep .5
echo "$foo$bar $doo$dah" # Do something with the entered information
done
I'm not entirely keen on read with no name after it, not least because it is a Bash extension rather than standard shell functionality (the POSIX read utility does not provide a default variable name), and omitting the name unnecessarily limits the scripts portability.
Note, too, that the here documents have the start marker enclosed in quotes so that the content of the here document is not subjected to shell expansions. The - indicates that leading tabs (but not spaces) are deleted from the here document and the end of document marker line.
The code in the question for the first loop could also be 'fixed' by using a continue instead of a nested while loop. However, if the second loop was to retry just the second prompt, continuing the outer loop and skipping the first menu would be complex. The solution shown is symmetric and properly isolates the input for each of the two prompts.
I chose not to re-echo the menu on a retry, but it would not be hard to loop on that code too. It would be feasible to use:
retries=0
answer=no
while clear
cat <<-'EOF'
======================
Foo Bar Doo Dah Setup
======================
(1) Foo
(2) Bar
(q) Quit
----------------------
EOF
read
do
…processing $REPLY as before…
done
However, doing so will cause many scratched heads as people are not often aware that you can have a list of commands after a while statement and it is only the exit status of the last that controls whether the loop continues another iteration or not.
I personally avoid tabs in shell scripts, so I'd probably create variables to hold the menus:
menu1=$(cat <<-'EOF'
======================
Foo Bar Doo Dah Setup
======================
(1) Foo
(2) Bar
(q) Quit
----------------------
EOF
)
The prompt in the loop could then be:
while clear; echo "$menu1"; read
which is easier to understand (and the double quotes are crucial). You could use && in place of ; if the clear command is well behaved and exits successfully.

bash - if return code >=1 re run script, start at the beginning

I have a bash script that prints a heading and tests for a value of "Y" or "N".
When someone enters text that does not equal "Y" or "N", I would like to send them back to the beginning of the script, so it prints the heading and the question again.
I know you can do this with goto but I was wondering if there's a different way because I hear many individuals say you should not use goto or that it is deprecated. Whether true or not, I'd like to see if anyone else has a way to solve this problem.
Thanks in advance.
You could implement it in a loop:
while [ !$exit_loop ]
do
echo "enter choice - "
read -n 1 input
case "$input" in
y|Y) $exit_loop = 1;;
n|N) $exit_loop = 1;;
*) echo "invalid choice";;
esac
done
Personally I find no difference between using a goto/loop or any other means. I'd always say to use what is most suitable for the situation - for yours, I'd use a goto.
e.g. If you have multiple indentations spanning lots of lines, and you need to jump back to the start of a function, I'd use a goto - it's a lot easier to understand in its context.
As a direct answer to your question, you can use exec to replace the current process with another process, or, as the case may be, another fresh copy of the current process.
read -p "Yes? Or no? " yn
case $yn in
[YyNn]) ;;
*) exec "$0" "$#"
esac
If you want a more structured approach, you can use a while or until loop.
Example (slightly simplified) using #Michael's suggestion follows. The exit condition is in the while loop, but the user can also do an intermediary action to decide which action to take:
while [[ ! "${action-}" =~ ^[SsRr]$ ]]
do
echo "What do you want to do?"
read -n 1 -p $'[d]iff, [s]kip, [S]kip all, [r]eplace, [R]eplace all: \n' action
if [[ "${action-}" =~ ^[Dd]$ ]]
then
diff "$target_path" "$source_path"
fi
done
echo "Hello User, are you ready to learn some Linux?"
while true; do
echo "Please enter y/n:"
read a
bfunc() {
if [ "$a" == "y" ]
then
echo "That is great, lets get started!"
echo "This Script is under construction, functionality coming soon"
exit 0
elif [ "$a" == "n" ]
then
echo "That is too bad, see you next time!"
echo "You are now exiting the script"
exit 0
else [ "$a" != "y||n" ]
echo "Please only enter y or n!"
fi
}
bfunc
done

Resources