Bash Dialog add buttons and functionality to buttons? - bash

I am essentially trying to move from python to bash to reduce overhead but still provide a little GUI functionality.
I have this code and found a way to add a button but I need to add another button and find out how to connect a function to the buttons? one button will call another program to run, the other will stop/kill that other program.
Can somebody show me an example of how to connect a function to a button?
#!/bin/bash
# useradd1.sh - A simple shell script to display the form dialog on screen
# set field names i.e. shell variables
call1="w5dmh"
call2="kd8pgb"
message="here is a message"
# open fd
exec 3>&1
# Store data to $VALUES variable
VALUES=$(dialog --extra-button --extra-label "Stop Beacon" --ok-label "Start Beacon" \
--backtitle "PSKBeacon Setup" \
--title "PSKBeacon" \
--form "Create a new beacon" \15 50 0 \
"From Callsign:" 1 1 "$call2" 1 18 8 0 \
"To Callsign:" 2 1 "$call1" 2 18 8 0 \
"Message:" 3 1 "$message" 3 18 25 0 \
2>&1 1>&3)
# close fd
exec 3>&-
beacon="$call1 $call1 de $call2 $call2 $message"
[ -e pskbeacon.txt ] && rm pskbeacon.txt
# display values just entered
echo $beacon >>pskbeacon.txt

The exit status of the dialog program tells you what "button" was chosen, and the output of a dialog invocation (which you save to the VALUES variable) contains the contents of the form's fields, assuming that one of the "ok" or "extra" choices were made. (You're labeling these "start" and "stop", respectively.)
So, you need to add some code to both check dialog's exit status, and extract values from VALUES. Something like the following, picking up the story after the dialog invocation:
# Just after the "Store data to $VALUES variable"
# VALUES=$(dialog ... )
ret=$?
# close fd
exec 3>&-
case "$ret" in
0) choice='START' ;; # the 'ok' button
1) echo 'Cancel chosen'; exit ;;
3) choice='STOP' ;; # the 'extra' button
*) echo 'unexpected (ESC?)'; exit ;;
esac
# No exit, so start or stop chosen, and dialog should have
# emitted values (updated, perhaps), stored in VALUES now
{
read -r call1
read -r call2
read -r message
} <<< "$VALUES"
# setting beacon differently: include choice, and quote form values
beacon="$choice: '$call1' de '$call2' '$message'"
[ -e pskbeacon.txt ] && rm pskbeacon.txt
# display values just entered
echo $beacon >>pskbeacon.txt
"Connecting" a function to button, as you say, can be as simple as testing the variable choice in my example:
if [[ "$choice" = 'START' ]]; then
echo 'Do something real here for START'
elif [[ "$choice" = 'STOP' ]]; then
echo 'Do something real here for STOP'
fi

Related

Whiptail: How can I handle an error when a gauge is running?

Here is a for loop, which is running a regular Whiptail gauge. I this loop, a command might throw an error and I would like to display a message box --msgbox displaying the error. After that, I could like the script to continue it's way like the error never happened.
Here is an oversimplified code as an example. Here is it's behavior and the code:
A for list is iterating on an array with 3 numbers and 1 letter.
For the 2 first numbers, a command using them is executed and a gauge is increasing by 25%.
For the letter, the command will fail, and I would like to display a --msgbox with the error waiting me to press enter
For the last number, the same behavior than for the 2 others.
#!/bin/bash
array=("1" "2" "A" "3")
i=0
for element in "${array[#]}"; do
command $element #This command will fail with any non int value.
echo "XXX"
echo $(expr 100 / 4 \* $i)
echo ${array[$i]}
echo "XXX"
i=$((i+1))
sleep 1
done | whiptail --title "Gauge" --gauge "Wait" 10 80 0
I have already tried few things like command $element || whiptail --title "Exception" --msgbox "Error!" 10 80. However, as the whiptail for the message box is in the loop for the gauge, the output is broken.
The problem is maybe coming from my design?
Thank you for your help :)
Even though it's been a while that this has been asked, you can output to a different output descriptor. In my case, I output to a separate log file like so:
#!/bin/bash
array=("1" "2" "A" "3")
i=0
for element in "${array[#]}"; do
command $element #This command will fail with any non int value.
echo "XXX" >> logs.log
echo $(expr 100 / 4 \* $i)
echo ${array[$i]} >> logs.log
echo "XXX" >> logs.log
i=$((i+1))
sleep 1
done | whiptail --title "Gauge" --gauge "Wait" 10 80 0

How to make my script menu execute my subscripts in Unix

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.

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.

sleep like function with user input intterupt

I wanted to do a shell function which countdown to n seconds (10 for exemple) and after that, continue the script.
I tried with the sleep function but it does stop the script entirely.
I want something like when the user input "y" during this countdown, it will stop the countdown and do something particular (much like an "interrupt").
And if the countdown finishes without any user input, the script continues.
Thank you !
**UPDATE* *
#Krzysztof Księżyk That's exactly what i wanted !
One difference, if i want the read return only if "Y" is the input how can i do that ? i already tried with the -d and -a...
Here is my code :
label="NTFS"
read -n 1 -t 5 MS
if [ -z "$MS" ]
then
echo "You don't input value, default will be taken"
else
echo -e "\nYou pressed 'Y' and want change default backup device."
read -p "Please input label of your secondary backup device: " secondary_label
label=$secondary_label
fi
echo "the choosen backup device label is $label"
You can use read command, eg.
read -n 1 -t 10
It will wait up to 10 second just for 1 character.
** UPDATE **
modified solution based on extra info from author
CNT=0
while [ $CNT -lt 10 ]; do
(( CNT++ ))
read -s -n 1 -t 1 -r KEY
if [ "$KEY" == 'y' ]; then
echo 'doing something'
else
echo 'doing nothing'
fi
done
If you want an interrupt, you probably want to trap SIGINT:
#!/bin/sh
trap : SIGINT
echo begin
for((i=0;i<10;i++)); do printf "$i\r"; sleep 1; done &
wait || kill $!
echo
echo end
The script counts to 10, but the timer aborts when the user sends a SIGINT (eg ctrl-C)

How to display the key pressed by the user?

please help how to add variables in this script, does anyone know? Thank you all.
#!/bin/bash
ARROW_UP=??? # I do not know
ARROW_DOWN=??? # I do not know
ARROW_LEFT=??? # I do not know
ARROW_RIGHT=??? # I do not know
ARROW_ENTER=??? # I do not know
case "$KEY" in "$ARROW_UP") echo "press the up arrow key"
"$ARROW_DOWN") echo "press the down arrow key";;
"$ARROW_LEFT") echo "press the left arrow key"
"$ARROW_RIGHT") echo "press the right arrow key"
"$ARROW_ENTER") echo "press the enter key"
esac
Try something like this (you might want to add a case to break out of the loop):
#!/bin/bash
# Reset terminal to current state when we exit.
trap "stty $(stty -g)" EXIT
# Disable echo and special characters, set input timeout to 0.2 seconds.
stty -echo -icanon time 2 || exit $?
# String containing all keypresses.
KEYS=""
# Set field separator to BEL (should not occur in keypresses)
IFS=$'\a'
# Input loop.
while [ 1 ]; do
# Read more input from keyboard when necessary.
while read -t 0 ; do
read -s -r -d "" -N 1 -t 0.2 CHAR && KEYS="$KEYS$CHAR" || break
done
# If no keys to process, wait 0.05 seconds and retry.
if [ -z "$KEYS" ]; then
sleep 0.05
continue
fi
# Check the first (next) keypress in the buffer.
case "$KEYS" in
$'\x1B\x5B\x41'*) # Up
KEYS="${KEYS##???}"
echo "Up"
;;
$'\x1B\x5B\x42'*) # Down
KEYS="${KEYS##???}"
echo "Down"
;;
$'\x1B\x5B\x44'*) # Left
KEYS="${KEYS##???}"
echo "Left"
;;
$'\x1B\x5B\x43'*) # Right
KEYS="${KEYS##???}"
echo "Right"
;;
esac
done
More details here.
The short answer is you can't. Use a real programming language.
Some overly complex solutions can be found here, but I don't endorse them. The KEYBD trap solution at the bottom of the same page is good, but requires ksh93.

Resources