How to replace/populate a bash case statement automatic - bash

How to improve the following batch script by making so whenever a new script is added into the directory, it doesn't require to manually hardcode an extra line of code in script.
like: 3) source $(pwd)/script-3.sh; myfunc;;
script:
#Menu script
title="Menu"
prompt="Pick an option(number): "
#options=(
# "script-1.sh" \
# "script-2.sh" \
# "script-3.sh" \
# )
path=$(pwd)
array=($path/*.sh)
options=( "${array[#]##*/}" )
echo "$title"
PS3="$prompt"
select opt in "${options[#]}" "Quit"; do
case "$REPLY" in
1) source $(pwd)/script-1.sh; myfunc;;
2) source $(pwd)/script-2.sh; myfunc;;
3) source $(pwd)/script-3.sh; myfunc;;
$((${#options[#]}+1))) echo "Goodbye!"; break;;
*) echo "Invalid option. Try another one.";continue;;
esac
done
How to automate this part ?
select opt in "${options[#]}" "Quit"; do
case "$REPLY" in
1) source $(pwd)/script-1.sh; myfunc;;
2) source $(pwd)/script-2.sh; myfunc;;
3) source $(pwd)/script-3.sh; myfunc;;
$((${#options[#]}+1))) echo "Goodbye!"; break;;
*) echo "Invalid option. Try another one.";continue;;
esac
done
this is the current manual part, where need to be hard-coded a value everytime a new script is added.
for example, lets say I added a new script called" script-99.sh
then it would need to hardcode it in the main script like this:
case "$REPLY" in
1) source $(pwd)/script-1.sh; myfunc;;
2) source $(pwd)/script-2.sh; myfunc;;
3) source $(pwd)/script-3.sh; myfunc;;
4) source $(pwd)/script-99.sh; myfunc;; ##Had to hardcode this line

A simple solution would be
select opt in "${options[#]}" "Quit"; do
script=script-$REPLY.sh
if [[ -f $script ]]
then
source $script
else
echo Goodbye
break
fi
done
This also catches the case that someone deletes a script while the select-loop is still in process.
A more stable solution would be
quitstring=Quit
select opt in "${options[#]}" $quitstring; do
[[ $opt == $quitstring ]] && break
source $opt
done

After lots attempts, come up with this:
select opt in "${options[#]}" "Quit"; do
source $(pwd)/$opt; myfunc
done
it is working!
however to exit the script it need to press Ctrl+z
this part of the code bellow which used to exit the script, I wasn't able to figure out how to adapt into the solution above.
$((${#options[#]}+1))) echo "Goodbye!"; break;;
*) echo "Invalid option. Try another one.";continue;;
so this is not a complete answer.

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

How to use case statements in bash by its index value?

I have the following code where the user picks a file type, then I run certain code for each case:
#!/bin/bash
PS3='Please enter your file type choice: '
options=(".c (C)" \
".cpp (C++)" \
".css (CSS)" \
".html (HTML)" \
".java (Java)" \
".ms (Groff)")
select option in "${options[#]}" # Asks for the option choice
do
case "${option}" in
".c (C)") # C programming file
echo "C OPTION SELECTED"
;;
".cpp (C++)") # C++ programming file
echo "C++ OPTION SELECTED"
;;
".css (CSS)") # CSS programming file
echo "CSS OPTION SELECTED"
;;
".html (HTML)") # HTML File
echo "HTML OPTION SELECTED"
;;
".java (Java)") # Java programming file
echo "JAVA OPTION SELECTED"
;;
".ms (Groff)") # Groff markup file
echo "GROFF OPTION SELECTED"
;;
*) echo "invalid option $option"
;;
esac
done
I was wondering how I can make it like this, where in the case statement you can address each case by the index of the array instead of the value of the array:
#!/bin/bash
PS3='Please enter your file type choice: '
options=(".c (C)" \
".cpp (C++)" \
".css (CSS)" \
".html (HTML)" \
".java (Java)" \
".ms (Groff)")
select option in "${options[#]}" # Asks for the option choice
do
case "${option}" in
1) # C programming file
echo "C OPTION SELECTED"
;;
2) # C++ programming file
echo "C++ OPTION SELECTED"
;;
3) # CSS programming file
echo "CSS OPTION SELECTED"
;;
4) # HTML File
echo "HTML OPTION SELECTED"
;;
5) # Java programming file
echo "JAVA OPTION SELECTED"
;;
6) # Groff markup file
echo "GROFF OPTION SELECTED"
;;
*) echo "invalid option $option"
;;
esac
done
I did a lot of research and I'm not the best at bash (but I'm learning). Sorry if the solution is obvious but I would appreciate your help, that you.
On bash 4.0 or newer, you can build a reverse index as an associative array:
declare -A options_reverse=()
for idx in "${!options[#]}"; do
val=${options[$idx]}
options_reverse[$val]=$idx
done
...after doing which, ${options_reverse[$option]} will map to the desired index.
Combined with the rest of your program, this would look like:
#!/bin/bash
case $BASH_VERSION in ''|[0-3].*) echo "ERROR: Bash 4.0+ required" >&2; exit 1;; esac
PS3='Please enter your file type choice: '
options=(
".c (C)"
".cpp (C++)"
".css (CSS)"
".html (HTML)"
".java (Java)"
".ms (Groff)"
)
declare -A options_reverse=()
for idx in "${!options[#]}"; do
val=${options[$idx]}
options_reverse[$val]=$idx
done
select option in "${options[#]}"; do
case "${options_reverse[$option]}" in
0) echo "C OPTION SELECTED";;
1) echo "C++ OPTION SELECTED";;
2) echo "CSS OPTION SELECTED";;
3) echo "HTML OPTION SELECTED";;
4) echo "JAVA OPTION SELECTED";;
5) echo "GROFF OPTION SELECTED";;
*) echo "invalid option $option";;
esac
done
From help option:
The line read is saved in the variable REPLY.

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 script has unexpected token `;;'

I have a Bash command that is generated by a buck Python script:
platform=$(case $(uname) in
"Linux") echo "linux-x86_64" ;;
"Darwin") echo "darwin64-x86_64-cc" ;;
*) echo "Unknown" esac);
cp -r $SRCDIR $OUT && cd $OUT && ./Configure shared $platform --prefix=$OUT/build --openssldir=$OUT/build/openssl && make && make install
When I try to execute it, I get the following error:
unexpected token `;;'
What have I done wrong here?
I am on the latest macOS.
A much clearer and more efficient way to write the code would be:
case $(uname) in
"Linux") platform="linux-x86_64" ;;
"Darwin") platform="darwin64-x86_64-cc" ;;
*) platform="Unknown" ;;
esac
Having the result of echo captured by $() is something of a shell anti-pattern. Why write to stdout and read from stdin when you can simply use the text directly?
The closing ) on the "Linux" case is terminating the process substitution. Since it's bash, just use matching parens in the case:
platform=$(case $(uname) in
("Linux") echo "linux-x86_64" ;;
("Darwin") echo "darwin64-x86_64-cc" ;;
(*) echo "Unknown";;
esac
)
(Also, note that I had to add ;; after the default case.)
I would use something like:
get_platform() {
case "${1,,}" in
darwin*) echo darwin64... ;;
freebsd*) echo freebsd... ;;
linux*) echo linux-x86_64.. ;;
cygwin*) echo cyg... ;;
*) return 1;;
esac
return 0
}
platform=$(get_platform "$OSTYPE" || get_platform $(uname))
echo "${platform:-Unknown}"
Double check - bash stores the operating system in the $OSTYPE variable. In case of fail (other shells), it still trying to use the uname.
In the case of undetected OS, the final decision, is populated to the main script (return 1) so you can check and adjust it as you want, like echo "${platform:-Unknown}".

Media Filesystem Type Validation

I am new to scripting and am at an impasse. I am working on a solaris system.
In the event the operator selects the wrong option (e.g. placed a dvd in the drive, but selects cdrom) the system responds with the following:
"hsfs mount: /dev/sr0 is not an hsfs file system"
What is the simplest way to modify the case statement to determine if the selected fs is correct (maybe use input from error mesg?), and if not, treat operator input as an invalid option to force a new selection?
#!/bin/bash
clear
echo " Please select media type (1,2) or quit (3):"
options=("CDROM" "DVD" "Quit")
select opt in "${options[#]}"
do
case $opt in
"CDROM")
echo "Mounting CDROM"
mount -F hsfs -r /dev/sr0 /cdrom
break
;;
"DVD")
echo "Mounting DVD"
mount -F udfs -r /dev/sr0 /cdrom
break
;;
"Quit")
break
;;
*) echo Invalid Option.;;
esac
done
Thank you for your time.
This should work (although untested):
#!/bin/bash
clear
echo " Please select media type (1) or quit (2):"
options=("CDROM/DVD" "Quit")
select opt in "${options[#]}"
do
case $opt in
"CDROM/DVD")
echo "Mounting CDROM/DVD"
fstyp=$(fstyp /dev/sr0)
case "$fstyp" in
[hu][sd]fs)
mount -F $(fstyp /dev/sr0) -r /dev/sr0 /cdrom ;;
"*") echo "Invalid media" ;;
esac
break
;;
"Quit")
break
;;
*) echo Invalid Option.;;
esac
done

Resources