How to make my script menu execute my subscripts in Unix - bash

Sorry in advance since i am new to Unix coding. I have a Bash shell script that generates 2 other subscripts. The main script implements a menu that gives a choice to the user in which script to generate.
I have two problems. The first one is how to make the script that the user selects to execute when he selects it, and the second how to implement input validation in my menu so when the user inputs something different that 1 and 2 to get an error message. This is my code so far:
#!/bin/bash
echo "Welcome to scriptGen."
echo "Please select a script to execute by choosing 1 or 2:"
scripts="bDir mMail"
select option in $scripts
do
echo "You have selected script $option to execute."
done
cat > bDir.sh <<EOF1
#!/bin/bash
#code
EOF1
chmod +x bDir.sh
cat >mMail.sh <<EOF2
#!/bin/bash
#code
EOF2
chmod +x mMail.sh
Thanks for your time!

So you'll probably want a case statement for your user to select their input.
Also, I'm not sure what you're doing with the dynamic writing of scripts, but you'll probably be better off with functions in your code.
Something like the following:
#!/bin/bash
main () {
echo "Welcome to scriptGen."
echo
echo "1: bDir"
echo "2: mMail"
echo -n "Please select a script to execute by choosing 1 or 2: "
read user_input
case $user_input in
1)
bdir
;;
2)
mmail
;;
*)
echo "Unrecognised option '$user_input'. Exiting..."
exit 1
;;
esac
}
bdir () {
echo "Running bDir"
}
mmail () {
echo "Running mmail"
}
main "$#"
Explanation:
main () {
...
}
Creating a function called main. It's a clear enough name to let the user know what function is going to get called first.
echo -n "Please select a script to execute by choosing 1 or 2: "
The -n removes the new-line at the end. This gives a nicer user experience when they're prompted.
read user_input
Read the user's input and store it in the variable user_input. The capture will finish when the user presses enter. However, this can be combined with other flags like -n 1 to capture just 1 character and continue without requiring the user to press enter.
case $user_input in
1)
bdir
;;
2)
mmail
;;
*)
echo "Unrecognised option '$user_input'. Exiting..."
exit 1
;;
esac
A case statement. Given the value of user_input, if it's 1, run the bdir function. If it's 2 run the mmail function. Otherwise, echo the warning and exit.
main "$#"
Run our main function. We use $# to ensure all of the variables passed into the script are also passed into the main function.

Related

Select and Case statements using BASH

The idea behind this code is to print options to the user and allow them to execute commands. The file name is example.sh. I simply run it by ./example.sh and it gives 3 options: author, type, exit.
NAME="Mark Zuck"
PS3="enter command: "
select var in author type exit
do
case $var in
author)
echo $NAME
;;
type)
cat $2
;;
exit)
exit
;;
esac
done
Whenever I type author, the output is Mark Zuck, but I am trying to execute the command "type".
After putting 2 <"file name">, it should give me the contents of the file.
cat $2 doesn't work. I don't know how to solve this issue.
1) author
2) type
3) exit
enter command: 2 file1
enter command:
The output is above.
It should give me the content of the "file1" (file1 does exist)
select returns two values:
The first is in your custom var and contains the token associated to the number in select list
The second is in REPLY (like built-in read command) and contains the number
Ok? it's the standard test case.
If you type another response on select prompt, var is nul (empty) and REPLY contains the response!
So, I think, this script is for you.
Of course, the coma (,) is not adapted for you, it's just an example. Use what you want.
#! /usr/bin/env bash
NAME="Mark Zuck"
PS3="enter command: "
select var in author type exit
do
case "$var,$REPLY" in
"author,1")
echo "$NAME"
;;
",2 "*)
filename="${REPLY#2 }"
echo "filename=<$filename>"
cat "${filename}"
;;
"exit,3")
exit
;;
esac
done
Test:
> bash --version
GNU bash, version 5.2.9(1)-release (x86_64-redhat-linux-gnu)
> ./test_select.sh
1) author
2) type
3) exit
enter command: 1
Mark Zuck
enter command: 2 test_select.sh
filename=<test_select.sh>
#! /usr/bin/env bash
NAME="Mark Zuck"
PS3="enter command: "
select var in author type exit
do
case "$var,$REPLY" in
"author,1")
echo "$NAME"
;;
",2 "*)
filename="${REPLY#2 }"
echo "filename=<$filename>"
cat "${filename}"
;;
"exit,3")
exit
;;
esac
done
enter command: 3
>

showing instructions for command line inputs in a bash script when run without inputs or wrong inputs

I have created a bash script which takes 2 command line arguments. It works absolutely fine.
I want to go a step further, and want to show the types of arguments it takes. i.e. if we run my_script.bash --help it should tell the desired arguments in its order.
my_script.bash is as follows
#!/bin/bash
X="$1" ## Name
Y="$2" ## address
echo " Mr./Ms. $X lives in $Y ."
Since it takes two arguments name and address, I want to show these when this bash is executed without any inputs or number of inputs or using my_script.bash --help command.
ie executing ./my_script.bash or ./my_script.bash --help should show like below
$ ./my_script.bash
>>this script takes two arguments. please enter **Name** and **address**
Since these arguments are position specific so we cannot change the positions of Name and Address. It would be great if we could pass the arguments by defining --name --address.
$ ./my_script.bash --address Delhi --name Gupta
>> Mr./Ms. Gupta lives in Delhi .
Any help would be appreciated.
A basic option parsing loop uses while and case, and looks like this:
print_help ()
{
cat <<-EOF
${0##*/} - process name and address
--name NAME your name
--address ADDRESS your address
--help this help
EOF
}
die ()
{
echo "$#" >&2
exit 1
}
while [[ $# -gt 0 ]]; do
case $1 in
--help)
print_help
exit
;;
--name)
shift || die "$1: requires input"
name=$1
;;
--address)
shift || die "$1: requires input"
address=$1
;;
*)
die "$1: invalid argument"
esac
shift
done
The order of options doesn't matter.
You can test for [[ $1 == --help ]], but since your script has 2 required arguments, you could simply print the help whenever the number of arguments is not equal 2:
if (( $# != 2 ))
then
echo You have to provide 2 arguments 1>&2
exit 1
fi

why is a bash read prompt not appearing when a script is run?

I have a bash script that prompts the user for different information based on what they're trying to do. The prompts are usually done with read -p. Usually it works just fine, the user sees what is being asked, enters what they need to enter, and everything does what it needs to do.
See the following (sanitized) snippet of a function in the script:
#!/bin/bash
function_name() {
if [ "$this_value" == "default" ];then
echo "Value set to default."
read -p "Enter desired value here: " desired_value
desired_value=${desired_value^^}
if [ "${#desired_value}" != 3 ] ;then
echo "$desired_value is an invalid entry."
exit 1
fi
if [ "$desired_value" != "$(some command that returns something to compare against)" ];then
echo "$desired_value is an invalid entry."
exit 1
fi
read -p "You entered $desired_value. Is this correct? [y/N] " reply
reply=${reply,,}
case "$reply" in
y|yes)
$some command that does what I want it to do
;;
*)
echo "User did not enter yes"
exit 1
;;
esac
fi
}
Usually the Enter desired value here and is this correct? lines appear just fine. But in a few instances I've seen, for some reason the read prompt is just blank. A user will see the following:
./script.bash
##unrelated script stuff
##unrelated script stuff
Value set to default.
user_entered_value_here
User did not enter yes. Exiting.
This is a real example that just happened that finally made me come here to ask what is going on (and I modified appropriately to make it an SO post).
What's happening is these two blank lines appear instead of the read -p text. For the first one, the user entered user_entered_value_here because they already know what is supposed to be entered there even without the read prompt. The second one, the Y/N prompt, they don't know, so they see it apparently hanging, and hit Enter instead of y, causing it to trigger the * case option.
I don't understand why the read -p text is not appearing, and especially why it's appearing for most users but not all users. I suspect there's some kind of environmental setting that causes this, but for the life of me I can't figure out what. This is being run only on RHEL 6.2, under bash 4.1.2.
I looked at the man of bash to catch some kind of detail about the read built-in. It is specified that -p option displays the "prompt on standard error, without a trailing newline, before attempting to read any input. The prompt is displayed only if input is coming from a terminal".
Let's consider the simple script input.sh:
#!/bin/bash
read -p "Prompt : " value
echo The user entered: "$value"
Example of execution:
$ ./input.sh
Prompt : foo
The user entered: foo
If stderr is redirected:
$ ./input.sh 2>/dev/null
foo
The user entered: foo
If the input is a pipe
$ echo foo | ./input.sh
The user entered: foo
If the input is a heredoc
$ ./input.sh <<EOF
> foo
> EOF
The user entered: foo
Rewrote your script with shell agnostic grammar and fixed some errors like comparing the string length with a string comparator != = rather than a numerical comparator -ne -eq:
#!/usr/bin/env sh
this_value=default
toupper() {
echo "$1" | tr '[:lower:]' '[:upper:]'
}
function_name() {
if [ "$this_value" = "default" ]; then
echo "Value set to default."
printf "Enter desired value here: "
read -r desired_value
desired_value=$(toupper "$desired_value")
if [ "${#desired_value}" -ne 3 ]; then
printf '%s is an invalid entry.\n' "$desired_value"
exit 1
fi
if [ "$desired_value" != "$(
echo ABC
: some command that returns something to compare against
)" ]; then
echo "$desired_value is an invalid entry."
exit 1
fi
printf 'You entered %s. Is this correct? [y/N] ' "$desired_value"
read -r reply
reply=$(toupper "$reply")
case $reply in
'y' | 'yes')
: "Some command that does what I want it to do"
;;
*)
echo "User did not enter yes"
exit 1
;;
esac
fi
}
function_name

bash script case statements not working using dialog

I've been experimenting with dialog and bash scripting lately. I created a dialog menu with three options: Users, Passwords, and Groups then also a quit option. I'm trying to run functions for each of these in a case statement but it always seems to fall through to the catchall last statement and output the echo "Something Else..." statement regardless of which option I choose instead of say running the echo statements in the related function, e.g. echo "Entering Users sub-menu"
I've tried running this with debugging on:
bash -x myscript
and I get the following output:
+ choice='Users
Error: Expected a box option.
Use --help to list options.'
+ '[' 'Users
Error: Expected a box option.
Use --help to list options.' '!=' ' Quit ']'
+ case $choice in
+ echo 'Something else. Where Am I?'
Something else. Where Am I?
+ exit 0
I'm still trying to figure out what "Expected a box option" means which sounds like its related to dialog but I'm also wondering if there's something broken with my if statement or case statement in bash.
the code to my script:
#!/bin/bash
function quit {
exit 0
}
function menu {
choice=$(dialog --backtitle "Rob Graves's Bash Scripting" \
--title "Main Menu" --menu "Choose one" 30 50 4 "Users" \
"- Do something with users" "Passwords"\
"- Do stuff with passwords" "Groups" "- Do things with groups" \
"Quit" "- Exit to desktop" --clear --nocancel 3>&1 1>&2 2>&3)
if [ "$choice" != "Quit" ]; then
case $choice in
Users)
users #calls users function
;;
Passwords)
passwords #calls passwords function
;;
Groups)
groups #calls groups function
;;
*)
echo "Something else. Where Am I?"
;;
esac
else
echo "Quitting..."
quit
fi
}
function users {
echo "Entering Users sub-menu"
}
function passwords {
echo "Entering Passwords sub-menu "
}
function groups {
echo "Entering Groups sub-menu"
menu
exit 0
Your immediate options seems to be that dialog command does not like the options --clear and --nocancel options at the end as you have mentioned. Re-ordering it seems to work fine as expected
choice=$(dialog --backtitle "Rob Graves's Bash Scripting" \
--title "Main Menu" \
--clear \
--nocancel \
--menu "Choose one" 30 50 4 \
"Users" "- Do something with users" \
"Passwords" "- Do stuff with passwords" \
"Groups" "- Do things with groups" \
"Quit" "- Exit to desktop" 3>&1 1>&2 2>&3)
Also it would be good idea to always quote your case option strings as below
case "$choice" in
"Users")
users #calls users function
;;
"Passwords")
passwords #calls passwords function
;;
"Groups")
groups #calls groups function
;;
*)
echo "Something else. Where Am I?"
esac
Also remember you can also add options for "Users" as "1 - Users" in both the dialog menu and in the case menu as below.
and in case statement as
case "$choice" in
"1 - Users")
Also note that the commands users(1) and groups(1) are standard commands available as part of GNU bash and using the same name for functions has a possibility of bringing an uncertainty. Always choose names that are unambiguous.
Remember to exit the script with a non-zero exit code on failure cases from the script. For e.g. on the default case above, remember to add an exit 1, so that it adds one other way of debug facility to look in, when the script exits abnormally, not running the expected flow of sequence.
*)
echo "Something else. Where Am I?"
exit 1
;;
When this is hit and when your script exits and doing echo $? would have shown the code returned.
Also drop the non-standard function keyword from the function definition in the script. As long as the script runs in bash shell it should be fine, on a pure POSIX only shell, the keyword might not be recognized.
Also you should use #!/usr/bin/env bash for portability: different *nixes put bash in different places, and using /usr/bin/env is a workaround to run the first bash found on the PATH.

Need way to approve bash cmd exec before running w/o affecting echo return

Currently trying to write bash that will do the following.
check if curl is installed
print out "which curl" before running it so that the user is able to opt in/out of running something they consider unsafe.
Use case is when you download a big script from github and you want to have more control over what it is doing. Also to be more aware of how it works.
I am not sure how to include this opt in/out code without messing up the "return" echo. Maybe the answer is to use something different that the read -n 1 -s -r -p code. I like that solution because it allows hitting any key to continue.
To be clear. If I check for YES/NO later on, it is messed up because it will contain the character used to continue by pressing any key. In my output example the space bar was hit to continue
#! /bin/bash
# Returns YES if installed otherwise return NO
check_curl_installed() {
echo >&2 "Before running, the command will be printed below."
echo >&2 "Press any key to approve running it"
read -n 1 -s -r -p "which curl"
echo ""
if which curl > /dev/null; then
echo "YES"
else
echo "NO"
fi
}
main() {
RESULT=$(check_curl_installed)
echo $RESULT
echo x${RESULT}x
}
main "$#"
exit 0
This is the output
user#computer:tmp$ ./check_curl_installed.sh
Before running, the command it will be printed below.
Press any key to approve running it
which curlYES
x YESx
Instead of using the output of the function, use its exit status.
check_curl_installed() {
echo >&2 "Before running, the command will be printed below."
echo >&2 "Press any key to approve running it"
read -n 1 -s -r -p "which curl"
echo ""
if which curl > /dev/null; then
return 0
else
return 1
fi
}
if check_curl_installed
then
# do something
else
# do something else
fi
how about this modified version to get only the Y key to answer...
#! /bin/bash
# Returns YES if installed otherwise return NO
check_curl_installed() {
echo >&2 "Before running, the command will be printed below."
echo >&2 "Press Y key to approve running it"
read -n 1 -r -s -p "Which curl?"
if [[ $REPLY =~ ^[Yy]$ ]]; then
if which curl > /dev/null ; then
printf "\nYES It is installed\n"
else
printf "\nNO It is not installed\n"
fi
else
printf "\nExiting - not checking\n"
fi
}
main() {
check_curl_installed
}
main "$#"
exit 0
Your echo result is, i think just pulling the first line of the check_curl_installed function...
Maybe if result was set to an array?
my testing around has shown that it's forgetting variables in the function at the higher main function. I even tried exporting to get the main function to work, but to no avail. I'm not super strong in bash, so i apologize on that.
Also might work better to put the echos inside each function, instead of shoving them into a variable...
Most, if not all, languages, only return one value from a function. Maybe this is why your output don't do as you want? a quick search brought this up https://unix.stackexchange.com/questions/408543/how-can-a-bash-function-return-multiple-values

Resources