Return to previous commands in bash script? - bash

I'm trying to implement a prev option in my bash script to go back to the previous "menu", as well as a way for the script to ask for user input again if no variable is set for $name.
Heres my bash script:
#!/bin/bash
#Menu() {
for (( ; ; ))
do
beginORload=
echo "Choose option:"
echo "1 - Begin"
echo "2 - Load"
read -p "?" beginORload
#}
#Begin() {
if [ "$beginORload" -eq "1" ]
then
clear
for (( ; ; ))
do
echo "Beginning. What is your name?"
read -p "?" name
#If "prev" specified, go back to #Menu()
if [ "$name" -eq "prev" ]
then
Menu
fi
#If nothing specified, return to name input
if [ -z ${name+x} ]
then
Begin
else
break
fi
echo "Hi $name !"
done
fi
done
In batch, I could simply do:
:menu
echo Choose option:
echo 1 - Begin
echo 2 - Load
[...]
:begin
[...]
if "%name%==prev" goto menu
if "%name%==" goto begin
the issue is I keep running into errors all over the place, and I can't figure out what to type to get it to work
im running Yosemite btw. Thankyou

Something like this is close to what you expect:
while [[ $answer -ne '3' ]];do
echo "Choose option:"
echo "1 - Begin"
echo "2 - Load"
echo "3 - Exit"
read -p "Enter Answer [1-2-3]:" answer
case "$answer" in
1) while [[ "$nm" == '' ]];do read -p "What is your Name:" nm;done # Keep asking for a name if the name is empty == ''
if [[ $nm == "prev" ]];then nm=""; else echo "Hello $nm" && break; fi # break command breaks the while wrapper loop
;;
2) echo 'Load' ;;
3) echo 'exiting...' ;; # Number 3 causes while to quit.
*) echo "invalid selection - try again";; # Selection out of 1-2-3 , menu reloaded
esac # case closing
done # while closing
echo "Bye Bye!"
As a general idea you can wrap up your case selection in a while loop which will break under certain circumstances (i.e If Option 3 is selected or if a valid name is given (not blank - not prev)
PS1: In bash you compare integers with -eq , -ne, etc but you compare strings with == or !=
PS2: Check the above code online here

Related

Pass prompt option to a file

I want to create a script where I have pre-defined option values.
opt1 opt2 opt3
I will start a script and it will ask me to choose from opt1 - opt3.
Once I pick e.g. opt2, that opt2 will then be passed as a variable.
How can I please achieve it?
You can use the "case" command to check if the user value is on a list of expected values:
#!/bin/bash
selected_option=""
echo " *** MENU ***"
echo "1) Opt1"
echo "2) Opt2"
echo "3) Opt3"
echo -n " Please, enter your option: "
read user_answer
case $user_answer in
1|2|3) selected_option="$user_answer"
;;
*) echo "Invalid Option!"
esac
##### Show the result only if the user selected an valid option #####
if [[ ! -z "${selected_option}" ]]; then
echo "Selected Option: [${selected_option}]"
fi
The '|' can be used to separate the valid options, and it will act as an "or" operator.
The '*' section will be executed if the previous condition is not satisfied, which means a "default" behavior, at this case it will display "Invalid Option" message.
Finally, the last if checks if the variable "${selected_option}" is empty, if not it is printed, but you can do whatever you want with that.
#!/bin/sh
echo "choose an option [ 1 opt1 | 2 opt2 | 3 opt3 ]"
read input
if [ $input == 1 ]
then
output="option 1 was chosen"
elif [ $input == 2 ]
then
output="option 2 was chosen"
elif [ $input == 3 ]
then
output="option 3 was chosen"
else
output="invalid input"
fi
echo "$output"

What is the best way to accept a 2nd user input from options defined by the 1st user input?

My background is in SQL but I've been learning Bash to create tools to help non-Linux users find what they need from my Linux system - I am pretty green with Bash, I apologize if this looks a bit dumb.
The goal of the script is to essentially display all directories within the current directory to the user, and allow them to input 1-9 to navigate to lower directories.
My sticking point is that I'm trying to use arrays to define potential filepaths, since in practice new directories will be added over time and it is not practical to edit the script each time a filepath is added.
Here's my prototype so far, currently it navigates into Test1, Test2, or Test3 then echos pwd to prove it is there.
#Global Variables
DIR_MAIN='/home/admin/Testhome'
#Potential Filepaths
#/home/admin/Testhome/Test1/Test1-1/
#/home/admin/Testhome/Test1/Test1-2/
#/home/admin/Testhome/Test2/Test2-1/
#/home/admin/Testhome/Test2/Test2-2/
#/home/admin/Testhome/Test3/Test3-1/
#/home/admin/Testhome/Test3/Test3-2/
#Defining Array for first user input
arr=($(ls $DIR_MAIN))
#System to count total number of directories in filepath, then present to user for numbered selection
cnt=0
for i in ${arr[#]}
do
cnt=$(($cnt+1))
echo "$cnt) $i"
done
read -p "Select a folder from the list: " answer
case $answer in
1)
cd $DIR_MAIN/${arr[0]}
echo "Welcome to $(pwd)"
;;
2)
cd $DIR_MAIN/${arr[1]}
echo "Welcome to $(pwd)"
;;
3)
cd $DIR_MAIN/${arr[2]}
echo "Welcome to $(pwd)"
;;
esac
I've tried the following, but it doesn't like the syntax (to someone experienced I'm sure these case statements look like a grenade went off in vim).
I'm beginning to wonder if the SELECT CASE road I'm going down is appropriate, or if there is an entirely better way.
#User Input Start
echo "What is the secret number?"
while :
do
read STRING1
case $STRING1 in
1)
echo "Enter the number matching the directory you want and I will go there"
echo "1 - ${arr[0]}"
echo "2 - ${arr[1]}"
echo "3 - ${arr[2]}"
read STRING2
case $STRING2 in
1)
cd $DIR_MAIN/${arr[0]}
echo "Welcome to" $(pwd)
2)
cd $DIR_MAIN/${arr[1]}
echo "Welcome to" $(pwd)
3)
cd $DIR_MAIN/${arr[2]}
echo "Welcome to" $(pwd)
*)
echo "Thats not an option and you know it"
*)
echo "1 is the secret number, enter 1 or nothing will happen"
;;
esac
#break needs to be down here somewhere
done
Ultimately I know I'll need to variabilize a local array once I'm in Test2 for example (since in practice, this could descend as far as /Test2/Test2-9 and there would be tons of redundant code to account for this manually).
For now, I'm just looking for the best way to present the /Test2-1 and /Test2-2 filepaths to the user and allow them to make that selection after navigating to /Test2/
This might do what you wanted.
#!/usr/bin/env bash
shopt -s nullglob
n=1
for i in /home/admin/Testhome/Test[0-9]*/*; do
printf '%d) %s\n' "$n" "$i"
array[n]="$i"
((n++))
done
(( ${#array[*]} )) || {
printf 'It looks like there is/are no directory listed!\n' >&2
printf 'Please check if the directories in question exists!\n' >&2
return 1
}
dir_pattern_indices=$(IFS='|'; printf '%s' "#(${!array[*]})")
printf '\n'
read -rp "Select a folder from the list: " answer
if [[ -z $answer ]]; then
printf 'Please select a number and try again!' >&2
exit 1
elif [[ $answer != $dir_pattern_indices ]]; then
printf 'Invalid option %s\n' "$answer" >&2
exit 1
fi
for j in "${!array[#]}"; do
if [[ $answer == "$j" ]]; then
cd "${array[j]}" || exit
printf 'Welcome to %s\n' "$(pwd)"
break
fi
done
The script needs to be sourced e.g.
source ./myscript
because of the cd command. See Why can't I change directory using a script.
Using a function instead of a script.
Let's just name the function list_dir
list_dir() {
shopt -s nullglob
declare -a array
local answer dir_pattern_indices i j n
n=1
for i in /home/admin/Testhome/Test[0-9]*/*; do
printf '%d) %s\n' "$n" "$i"
array[n]="$i"
((n++))
done
(( ${#array[*]} )) || {
printf 'It looks like there is/are no directory listed!\n' >&2
printf 'Please check if the directories in question exists!\n' >&2
return 1
}
dir_pattern_indices=$(IFS='|'; printf '%s' "#(${!array[*]})")
printf '\n'
read -rp "Select a folder from the list: " answer
if [[ -z $answer ]]; then
printf 'Please select a number and try again!' >&2
return 1
elif [[ $answer != $dir_pattern_indices ]]; then
printf 'Invalid option %s\n' "$answer" >&2
return 1
fi
for j in "${!array[#]}"; do
if [[ $answer == "$j" ]]; then
cd "${array[j]}" || return
printf 'Welcome to %s\n' "$(pwd)"
break
fi
done
}
All of the array names and variables are declared local to the function in order not to pollute the interactive/enviromental shell variables.
Put that somewhere in your shellrc file, like say in ~/.bashrc then source it again after you have edited that shellrc file.
source ~/.bashrc
Then just call the function name.
list_dir
I took what #Jetchisel wrote and ran with it - I see they updated their code as well.
Between that code and what I hacked together piggybacking off what he wrote, I'm hoping future viewers will have what they need to solve this problem!
My code includes a generic logging function (can write to a log file if you define it and uncomment those logging lines, for a script this size I just use it to output debugging messages), everything below is the sequence used.
As he mentioned the "0" element needs to be removed from the array for this to behave as expected, as a quick hack I ended up assigning array element 0 as null and adding logic to ignore null.
This will also pull pretty much anything in the filepath, not just directories, so more tweaking may be required for future uses but this serves the role I need it for!
Thank you again #Jetchisel !
#hopt -s nullglob
DIR_MAIN='/home/admin/Testhome'
Dir_Cur="$DIR_MAIN"
LOG_LEVEL=1
array=(NULL $(ls $DIR_MAIN))
########FUNCTION LIST#########
####Generic Logging Function
Log_Message()
{
local logLevel=$1
local logMessage=$2
local logDebug=$3
local dt=$(date "+%Y-%m-%d %T")
##Check log level
if [ "$logLevel" == 5 ]
then local logLabel='INFO'
elif [ "$logLevel" == 1 ]
then local logLabel='DEBUG'
elif [ "$logLevel" == 2 ]
then local logLabel='INFO'
elif [ "$logLevel" == 3 ]
then local logLabel='WARN'
elif [ "$logLevel" == 4 ]
then local logLabel='ERROR'
fi
##Check conf log level
if [ "$LOG_LEVEL" == 1 ]
then #echo "$dt [$logLabel] $logMessage" >> $LOG_FILE ##Log Message
echo "$dt [$logLabel] $logMessage" ##Echo Message to Terminal
##Check if Debug Empty
if [ "$logDebug" != "" ]
then #echo "$dt [DEBUG] $logDebug" >> $LOG_FILE ##Extra Debug Info
echo "$dt [DEBUG] $logDebug" ##Extra Debug Info
fi
elif [ "$logLevel" -ge "$LOG_LEVEL" ]
then #echo "$dt [$logLabel] $logMessage" >> "$LOG_FILE" ##Log Message
echo "$dt [$logLabel] $logMessage"
fi
}
####
####Function_One
##Removes 0 position in array by marking it null, generates [1-X] list with further filepaths
Function_One()
{
Log_Message "1" "entered Function_One"
local local_array=("$#")
Log_Message "1" "${local_array[*]}"
n=1
for i in "${local_array[#]}"; do
if [ "$i" != "NULL" ]
then
printf '%d) %s\n' "$n" "$i"
array[n]="$i"
((n++))
fi
done
printf '\n'
read -rp "Select a folder from the list: " answer
for j in "${!local_array[#]}"; do
if [[ $answer == "$j" ]]; then
cd "$Dir_Cur/${local_array[j]}" || exit
printf 'Welcome to %s\n' "$(pwd)"
break
fi
done
}
####
########FUNCTION LIST END#########
########MAIN SEQUENCE########
echo "Script start"
Function_One "${array[#]}"
Dir_Cur="$(pwd)"
array2=(NULL $(ls $Dir_Cur))
Function_One "${array2[#]}"
Dir_Cur="$(pwd)"
$Dir_Cur/test_success.sh
echo "Script end"
########

Terminate shell script after three invalid input

Restricting user from trying multiple invalid attempt in shell scripting. I wrote the below script but somehow it's not getting me desire output. I have shared the script and script output both. Kindly help. Here I wanted script to terminate if user tried more than 3 times.
While true
do
echo -n "Enter yes or no"
read opt
case $opt in
yes) break ;;
no) break ;;
*) echo "Invalid input"
while [[ $err -le 3 ]]
do
If [[ $err -le 3 ]]
then
echo "err: $err"
((err++))
break
else
echo "Max limit crossed"
exit 1
fi
done
;;
esac
done
This was a nice question and I had a lot of fun solving it. I have to mention that I'm new to shell programming.
n=0
until [ $n -ge 3 ]
do
read line
if [ "$line" = "XYZ" ]; then
echo "Accepted"
break
else
n=$[$n+1]
echo " trying " $n "times "
fi;
done
This article helped me a lot to solve it.
Try:
#!/bin/bash
ANSWER=
max=3
while true; do
echo "Enter yes or no:"
read -r ANSWER
[[ $ANSWER == "yes" || $ANSWER == "no" ]] && break
echo Invalid Input
ANSWER=
((--max))
[[ $max -le 0 ]] && { echo "Max limit crossed"; exit 1; }
done

How can I make this multi-select Bash script default to all items selected?

I modified a great script I found online by Nathan Davieau on serverfault.com, but I have hit the limit to my knowledge of Bash!
It works great, apart from I have to select each item, I want it to start with all items selected and have to deselect them.
#!/bin/bash
ERROR=" "
declare -a install
declare -a options=('php' 'phpmyadmin' 'php-mysqlnd' 'php-opcache' 'mariadb-server' 'sendmail')
#=== FUNCTION ========================================================================
# NAME: ACTIONS
# DESCRIPTION: Actions to take based on selection
# PARAMETER 1:
#=======================================================================================
function ACTIONS() {
for ((i = 0; i < ${#options[*]}; i++)); do
if [[ "${choices[i]}" == "+" ]]; then
install+=(${options[i]})
fi
done
echo "${install[#]}"
}
#=== FUNCTION ========================================================================
# NAME: MENU
# DESCRIPTION: Ask for user input to toggle the name of the plugins to be installed
# PARAMETER 1:
#=======================================================================================
function MENU() {
echo "Which packages would you like to be installed?"
echo
for NUM in "${!options[#]}"; do
echo "[""${choices[NUM]:- }""]" $((NUM + 1))") ${options[NUM]}"
done
echo "$ERROR"
}
#Clear screen for menu
clear
#Menu loop
while MENU && read -e -p "Select the desired options using their number (again to uncheck, ENTER when done): " -n2 SELECTION && [[ -n "$SELECTION" ]]; do
clear
if [[ "$SELECTION" == *[[:digit:]]* && $SELECTION -ge 1 && $SELECTION -le ${#options[#]} ]]; then
((SELECTION--))
if [[ "${choices[SELECTION]}" == "+" ]]; then
choices[SELECTION]=""
else
choices[SELECTION]="+"
fi
ERROR=" "
else
ERROR="Invalid option: $SELECTION"
fi
done
ACTIONS
Here you go, you only need to initialize the choices[] (array) before you actually process them. (re-)Revised code below (with thanks to Charles Duffy) :
#!/bin/bash
ERROR=" "
declare -a install
declare -a options=('php' 'phpmyadmin' 'php-mysqlnd' 'php-opcache' 'mariadb-server' 'sendmail')
############### HERE's THE NEW BIT #################
declare -a choices=( "${options[#]//*/+}" )
####################################################
#=== FUNCTION ========================================================================
# NAME: ACTIONS
# DESCRIPTION: Actions to take based on selection
# PARAMETER 1:
#=======================================================================================
function ACTIONS() {
for ((i = 0; i < ${#options[*]}; i++)); do
if [[ "${choices[i]}" == "+" ]]; then
install+=(${options[i]})
fi
done
echo "${install[#]}"
}
#=== FUNCTION ========================================================================
# NAME: MENU
# DESCRIPTION: Ask for user input to toggle the name of the plugins to be installed
# PARAMETER 1:
#=======================================================================================
function MENU() {
echo "Which packages would you like to be installed?"
echo
for NUM in "${!options[#]}"; do
echo "[""${choices[NUM]:- }""]" $((NUM + 1))") ${options[NUM]}"
done
echo "$ERROR"
}
#Clear screen for menu
clear
#Menu loop
while MENU && read -e -p "Select the desired options using their number (again to uncheck, ENTER when done): " -n2 SELECTION && [[ -n "$SELECTION" ]]; do
clear
if [[ "$SELECTION" == *[[:digit:]]* && $SELECTION -ge 1 && $SELECTION -le ${#options[#]} ]]; then
((SELECTION--))
if [[ "${choices[SELECTION]}" == "+" ]]; then
choices[SELECTION]=""
else
choices[SELECTION]="+"
fi
ERROR=" "
else
ERROR="Invalid option: $SELECTION"
fi
done
ACTIONS
Sorry, but I can't afford the time to give a blow by blow description of how all this works. In this case, the traditional shell debugging tool of set -vx (paired with set +vx) might be a challenge to interpret for a newbie. Experiment only when you can spare the time.
Note that the key bit of code is where the + gets toggled :
if [[ "${choices[SELECTION]}" == "+" ]]; then
choices[SELECTION]=""
else
choices[SELECTION]="+"
fi
IHTH

Bash Input Variables & Loop

My script
#!/bin/bash
while :
do
echo "[*] 1 - Create a gnome-terminal"
echo "[*] 2 - Display Ifconfig"
echo -n "Pick an Option > "
read Input
if [ "$Input" = "1" ]; then
gnome-terminal
sleep 3
echo "[*] 1 - Create a gnome-terminal"
echo "[*] 2 - Display Ifconfig"
echo -n "Pick an Option > "
read Input
fi
if [ "$Input" = "2" ]; then
clear
ifconfig
echo "[*] 1 - Create a gnome-terminal"
echo "[*] 2 - Display Ifconfig"
echo -n "Pick an Option > "
read Input
fi
done
Doesn't reuse the Input variable, say if you want to run the ifconfig option multiple times in a row. It rechecks the first variable string to see if it matches "1", and when it doesn't it echoes back the list of options. Any ideas on how to make any input variable accessible regardless of when it is used?
The problem is that you're writing out the menu twice on each iteration. Stop doing that:
#!/bin/bash
while :
do
echo "[*] 1 - Create a gnome-terminal"
echo "[*] 2 - Display Ifconfig"
echo -n "Pick an Option > "
read Input
if [ "$Input" = "1" ]; then
gnome-terminal
sleep 3
fi
if [ "$Input" = "2" ]; then
clear
ifconfig
fi
done
Use select instead of rolling your own menu:
choices=(
"Create a gnome-terminal"
"Display Ifconfig"
)
PS3="Pick an Option > "
select action in "${choices[#]}"; do
case $action in
"${choices[0]}") gnome-terminal; sleep 3;;
"${choices[1]}") clear; ifconfig;;
*) echo invalid selection ;;
esac
done
select gives you an infinite loop by default. If you want to break out of it, put a break statement into the case branch.

Resources