I'm searching for good explanation about making dynamic dialog --menu box in bash. I'm trying to load a list of users from a file that have structure like this:
------ user ------
/rw412 0.2 /rx511 23.1 /sgo23 9.2
/fs352 1.4 /...
------ another_user ------
/rw412 0.3 / and so on...
of course the user name is between ------
i don't really know how to use loops inside dialog. I'm also trying to avoid creating additional files.
Please help
Here's an example of one way to use dialog. The options array can be built up in a variety of ways (see below).
#!/bin/bash
cmd=(dialog --keep-tite --menu "Select options:" 22 76 16)
options=(1 "Option 1"
2 "Option 2"
3 "Option 3"
4 "Option 4")
choices=$("${cmd[#]}" "${options[#]}" 2>&1 >/dev/tty)
for choice in $choices
do
case $choice in
1)
echo "First Option"
;;
2)
echo "Second Option"
;;
3)
echo "Third Option"
;;
4)
echo "Fourth Option"
;;
esac
done
Here's one way to build the options array:
count=0
while read -r dashes1 username dashes2
do
if [[ $dashes1 == --*-- && $dashes2 == --*-- ]]
then
options+=($((++count)) "$username")
fi
done < inputfile
following the above clues and having my own ideas as well; here is another way:
#!/bin/bash
MENU_OPTIONS=
COUNT=0
for i in `ls`
do
COUNT=$[COUNT+1]
MENU_OPTIONS="${MENU_OPTIONS} ${COUNT} $i off "
done
cmd=(dialog --separate-output --checklist "Select options:" 22 76 16)
options=(${MENU_OPTIONS})
choices=$("${cmd[#]}" "${options[#]}" 2>&1 >/dev/tty)
for choice in $choices
do
" WHATEVER from HERE"
done
Ok
Following Dennis Williamson clues and my own ideas i came to something like this
#!/bin/bash
c=0
while read -r dashes1 username dashes2
do
if [[ $dashes1 == --*-- && $dashes2 == --*-- ]]
then
options=("${options[#]}" "$((++c))" "$username")
fi
done < inputfile
cmd=(dialog --backtitle "title" --menu "Select_user:" 22 38 2) #2 becouse
i know there will be 2 options
command=`echo "${cmd[#]}" "${options[#]}" "2>file"`
$command
Now, there is an error like this:
Error: Expected 2 arguments, found only 1.
Why is that??
This is a repost of my answer from a very similar question. I arrived at this answer with the help of a cohort but couldn't find it in the wild; I think adding it here might help others.
The ${options[#]} array required the following (rather strange) structure in order to work. This was tested on bash in Ubuntu 18.04:
#Dynamic dialogs require an array that has a staggered structure
#array[1]=1
#array[2]=First_Menu_Option
#array[3]=2
#array[4]=Second_Menu_Option
Here is bash code that reads in a directory listing from an argument and creates a dynamic menu from it:
#! /bin/bash
#usage: Dynamic_Menu.bash /home/user/target_directory
declare -a array
i=1 #Index counter for adding to array
j=1 #Option menu value generator
while read line
do
array[ $i ]=$j
(( j++ ))
array[ ($i + 1) ]=$line
(( i=($i+2) ))
done < <(find $1 -type f) #consume file path provided as argument
#Define parameters for menu
TERMINAL=$(tty) #Gather current terminal session for appropriate redirection
HEIGHT=20
WIDTH=76
CHOICE_HEIGHT=16
BACKTITLE="Back_Title"
TITLE="Dynamic Dialog"
MENU="Choose a file:"
#Build the menu with variables & dynamic content
CHOICE=$(dialog --clear \
--backtitle "$BACKTITLE" \
--title "$TITLE" \
--menu "$MENU" \
$HEIGHT $WIDTH $CHOICE_HEIGHT \
"${array[#]}" \
2>&1 >$TERMINAL)
Related
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
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
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.
I have a script written in bash.
In one place I have a zenity list with a few options:
menu=("first option" "second option" "third option")
answer=`zenity --list --column=Menu "${menu[#]}" --height 170`
After that, when user choose first option I want to do something, when user choose second - something else, etc.
But idk how can I use the variable answer in If statement:
if [ option1 ]; then
...do something...
elif [ option2 ]; then
... do something else...
else
... do something different...
fi
When I chose first option and uses: echo "$answer", it printed "first option"
But when I tried:
if [ $answer = "first option" ]; then
....
fi
It didn't work.
You have to put $answer in "" to avoid word splitting:
#!/usr/bin/env bash
menu=("first option" "second option" "third option")
answer=`zenity --list --column=Menu "${menu[#]}" --height 170`
if [ "$answer" = "first option" ]; then
printf "First option has been chosen\n"
fi
Also, it's recommended to use $(...) instead of backticks. Replace:
answer=`zenity --list --column=Menu "${menu[#]}" --height 170`
with:
answer=$(zenity --list --column=Menu "${menu[#]}" --height 170)
EDIT:
I was also going to suggest using case here but it has already been suggested in the other answer.
Except for the quoting, if [ "$answer" = "first option" ] should work. You might want to use a case statement here:
case "$answer" in
"${menu[0]}")
echo do stuff for first
;;
"${menu[1]}")
echo do stuff for second
;;
"${menu[2]}")
echo do stuff for third
;;
esac
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