Search code examples
bashwhiptail

Bash incantation for defining whiptail radiolist programmatically


I want to issue a bash command similar to this:

whiptail --title 'Select Database' --radiolist 'Select Database:' 10 80 2 \
  1 production off \
  2 localhost  on

Whiptail is rather particular about how the radio list values are specified. They must be provided each on their own line, as shown. Here is a good article on this question.

The list of databases is available in a variables called DBS, and ACTIVE_DB is the radiolist item to highlight in the whiptail dialog.

Here is my current effort for building the command line. It is probably way too convoluted.

DBS="production localhost"
ACTIVE_DB="localhost"
DB_COUNT="$( echo "$DBS" | wc -w )"

DB_LIST="$(
  I=1
  echo ""
  for DB in $DBS; do
    SELECTED="$( if [ "$DB" == "$ACTIVE_DB" ]; then echo " on"; else echo " off"; fi )"
    SLASH="$( if (( $I < $DB_COUNT )); then echo \\; fi )"
    echo "  $I $DB $SELECTED $SLASH"
    echo ""
    I=$(( I + 1 ))
  done
)"

OPERATION="whiptail \
  --title \"Select Database\" \
  --radiolist \
  \"Select Database:\" \
  10 80 $DB_COUNT \"${DB_LIST[@]}\""

eval "${OPERATION}"

I get fairly close. As you can see, the expansion contains single quotes that mess things up, and backslashes are missing at some EOLs:

set -xv 
++ whiptail --title 'Select Database' --radiolist 'Select Database:' 10 80 2 '
  1 production  off
  2 localhost  on '

The solution needs to provide a way to somehow know which selection the user made, or if they pressed ESC. ESC usually sets the return code to 255, so that should not be difficult, however this problem gets really messy when trying to retrieve the value of the user-selected radiolist item.


Solution

  • The following follows best practices set out in BashFAQ #50:

    # note that lower-case variable names are reserved for application use by POSIX
    # see https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
    active_db="localhost"
    dbs=( production localhost ) # using an array, not a string, means ${#dbs[@]} counts
    
    # initialize an array with our explicit arguments
    whiptail_args=(
      --title "Select Database"
      --radiolist "Select Database:"
      10 80 "${#dbs[@]}"  # note the use of ${#arrayname[@]} to get count of entries
    )
    
    i=0
    for db in "${dbs[@]}"; do
      whiptail_args+=( "$((++i))" "$db" )
      if [[ $db = "$active_db" ]]; then    # only RHS needs quoting in [[ ]]
        whiptail_args+=( "on" )
      else
        whiptail_args+=( "off" )
      fi
    done
    
    # collect both stdout and exit status
    # to grok the file descriptor switch, see https://stackoverflow.com/a/1970254/14122
    whiptail_out=$(whiptail "${whiptail_args[@]}" 3>&1 1>&2 2>&3); whiptail_retval=$?
    
    # display what we collected
    declare -p whiptail_out whiptail_retval
    

    While I don't have whiptail handy to test with, the exact invocation run by the above code is precisely identical to:

    whiptail --title "Select Database" \
             --radiolist "Select Database:" 10 80 2 \
              1 production off \
              2 localhost on 
    

    ...as a string which, when evaled, runs the precise command can be generated with:

    printf '%q ' whiptail "${whiptail_args[@]}"; echo