I'm using Dialog, in bash, to create a simple multiple choice question. My options all include spaces in the values. When the Dialog is constructed, it appears to be building the array with a space delimiter.
My options are as such:
TITLE="Setup Script..."
VER="0.2Β"
{ # Default Dialog Values.
# └──These can be overwritten if need be.
DIALOG_TOGGLE="OFF" # Used for dialog multiple choice.
DIALOG_HEIGHT=15 # Sets the height.
DIALOG_WIDTH=78 # Sets the width.
DIALOG_CHOICES=4 # Sets the number of choices.
DIALOG_OK_LABEL="Submit" # Sets the OK button label.
DIALOG_CANCEL_LABEL="Cancel" # Sets the Cancel button label.
DIALOG_EXTRA_BUTTON=true # Enable/Disable the extra button.
DIALOG_EXTRA_LABEL="Select All" # Sets the "EXTRA" button label.
DIALOG_BACKTITLE=$TITLE" v:"$VER # Sets the background title.
DIALOG_TITLE="Example Title..." # Sets the dialog title.
DIALOG_DESCRIPTION="\n\
Welcome to the $TITLE\n\n\
Example Description"
declare -a DIALOG_OPTIONS=(
"Example Item 1" "This is an example option" $DIALOG_TOGGLE
"Example Item 2" "This is an example option" $DIALOG_TOGGLE
"Example Item 3" "This is an example option" $DIALOG_TOGGLE
)
}
My Dialog is then constructed as such:
load_dialog() {
# Check whether or not to display the extra button
if [ "$DIALOG_EXTRA_BUTTON" = true ] ; then
EB_TOGGLE='--extra-button'
else
EB_TOGGLE=''
fi
# Dialog Framework
cmd=(dialog
--colors
--clear
--keep-tite
--backtitle "$DIALOG_BACKTITLE"
--title "$DIALOG_TITLE"
--ok-label "$DIALOG_OK_LABEL"
--cancel-label "$DIALOG_CANCEL_LABEL"
$EB_TOGGLE
--extra-label "$DIALOG_EXTRA_LABEL"
--checklist "${DIALOG_DESCRIPTION//$'\t'/}"
"$DIALOG_HEIGHT"
"$DIALOG_WIDTH"
"$DIALOG_CHOICES"
)
# Build Dialog
CHOICES="$("${cmd[@]}" "${DIALOG_OPTIONS[@]}" 2>&1 1>/dev/tty)"
# Detect the exit status of the Dialog (what button was pressed)
exitStatus=$?
# Backup existing IFS
SAVEIFS=$IFS
# Set new IFS
IFS=$'\n'
# Determine what choices were selected
if [ -z "${CHOICES}" ]; then
echo "No option was selected..."
echo "└── User hit Cancel or unselected all options."
else
for CHOICE in ${CHOICES[@]}; do
case $CHOICE in
$CHOICE)
echo "${CHOICE} enabled."
;;
*)
echo "Something went wrong!"
;;
esac
done
fi
# Restore backed up IFS
IFS=$SAVEIFS
case $exitStatus in
0)
echo $DIALOG_OK_LABEL 'chosen';
;;
1)
echo $DIALOG_CANCEL_LABEL 'chosen';
exit
;;
3)
echo $DIALOG_EXTRA_LABEL 'chosen';
DIALOG_TOGGLE=on # set all to on
load_dialog
;;
*)
echo 'unexpected (ESC?)'; exit ;;
esac
}
}
I'm attempting to build this dialog as a sort of "constructor" so that I can pass it some different data, and build different dialogs on the fly. I'm attempting to not have to repeat the code of building the dialog each time. It's possible I may have a few prompts in my script along the way.
Ultimately, I'm trying to be able to differentiate the options picked by the user, as well as handling the exitstatus
of the dialog when a button is selected.
As it stands now, when I run this, the CHOICES="$("${cmd[@]}"...
seems to collect the values and build the array as such:
"Example Item 1" "Example Item 2" "Example Item 3"
Assuming all 3 options are selected of course.
I want to be able to handle the array as such: (Maybe even as CSV, instead of SSV)
"Example Item 1"
"Example Item 2"
"Example Item 3"
The issue I'm having, is trying to find a way to parse these items individually. I've been playing around with the IFS
value, and that seems to work for ignoring the spaces in the values themselves, but from what I gather, this also then ignores the spaces separating the values themselves.
I'm a bit confused, and I've been stuck on this for a while now. I feel like I'm stuck in a catch 22 here. Any advice or help on how I can better handle this data?
Assumptions:
dialog
) as a separate element in a bash
array named CHOICES
First item of interest:
CHOICES="$("${cmd[@]}" "${DIALOG_OPTIONS[@]}" 2>&1 1>/dev/tty)"
This does not populate an array but rather populates the CHOICES
variable with a single string of text (in this case the output from the dialog
call):
$ typeset -p CHOICES
declare -- CHOICES="\"Example Item 1\" \"Example Item 2\" \"Example Item 3\""
The normal approach for populating an array would look like:
CHOICES=( $("${cmd[@]}" "${DIALOG_OPTIONS[@]}" 2>&1 1>/dev/tty) )
But upon pushing this change into OP's code we find ourselves with the following array structure:
$ typeset -p CHOICES
declare -a CHOICES=([0]="\"Example" [1]="Item" [2]="1\"" [3]="\"Example" [4]="Item" [5]="2\"" [6]="\"Example" [7]="Item" [8]="3\"")
Primary issue appears to be that dialog
is passing the data back in a format that bash
cannot (easily) parse into 3 array elements.
While it's possible to write some (bash
) code to manually parse the dialog
results into 3 separate elements, let's first see if dialog
has any flags for passing the data back in a format that's easier to parse in bash
. From man dialog
I found the following:
--separate-output
For certain widgets (buildlist, checklist, treeview), output result one line
at a time, with no quoting. This facilitates parsing by another program.
Since OP's code is using the checklist widget (--checklist
) this looks like a possible solution. (NOTE: another option that may work, especially if using something other than the buildlist/checklist/treeview widgets, would be --output-separator
.)
If we add --separate-output
to OP's cmd=( ... )
construct, and switch back to OP's original string population of the CHOICES
variable, we see that our selected items are in fact returned on separate lines:
cmd=(dialog --colors --clear --separate-output ... )
CHOICES="$("${cmd[@]}" "${DIALOG_OPTIONS[@]}" 2>&1 1>/dev/tty)"
$ typeset -p CHOICES
declare -- CHOICES="Example Item 1
Example Item 2
Example Item 3"
We can now make use of the bash
builtin mapfile
, and process substitution, to capture each of these lines of output as a separate array element:
mapfile -t CHOICES < <( "${cmd[@]}" "${DIALOG_OPTIONS[@]}" 2>&1 1>/dev/tty )
$ typeset -p CHOICES
declare -a CHOICES=([0]="Example Item 1" [1]="Example Item 2" [2]="Example Item 3")
$ for choice in "${CHOICES[@]}"; do echo "you chose: ${choice}"; done
you chose: Example Item 1
you chose: Example Item 2
you chose: Example Item 3
As for the exitStatus
issue ...
Capturing the return status of a process substitution requires a bit of wrangling. (A web search on bash return status process substitution
should bring up a wide range of approaches.)
We'll take a simple approach and just write the return/exit code to a local file (.exit_status
):
mapfile -t CHOICES < <( "${cmd[@]}" "${DIALOG_OPTIONS[@]}" 2>&1 1>/dev/tty; echo $? > .exit_status )
read -r exitStatus < .exit_status
#rm .exit_status # uncomment line to remove the file
NOTES:
Select All
), OP's current code willexitStatus=3
load_dialog()
functionNet changes to OP's current code:
--separate-output
to the cmd=(dialog --colors ....)
array definitionCHOICES="$("${cmd[@]}" "${DIALOG_OPTIONS[@]}" 2>&1 1>/dev/tty)"
mapfile -t CHOICES < <( "${cmd[@]}" "${DIALOG_OPTIONS[@]}" 2>&1 1>/dev/tty; echo $? > .exit_status )
exitStatus=$?
read -r exitStatus < .exit_status
rm .exit_status
)IFS
(this is not needed and just clutters up the code)${CHOICES[@]}
becomes "${CHOICES[@]}"
; $CHOICE
becomes "$CHOICE"
$exitStatus
becomes "$exitStatus"