Originally published November 27, 2017 @ 10:49 pm

Whiptail is a newt-based utility allowing to build pseudo-graphical dialog boxes from shell scripts. Dialog uses ncurses and is similar to whiptail but has more options and, consequently, a bit harder to use. I find both useful when a user needs to be guided through a complex set of variables and to minimize fat-fingering.

The best to understand how to use dialog and whiptail (and sometimes you would want to use both of them in the same script) is by looking at some example. In this first example we’re prompting the user for database connection information to build the ${MYSQL} connection string:

get_db_host() {
	db_host=$(whiptail --inputbox "Database host:" 8 78 "localhost" --title "Query Dialog" 3>&1 1>&2 2>&3)
	db_port=$(whiptail --inputbox "Database port:" 8 78 "3336" --title "Query Dialog" 3>&1 1>&2 2>&3)
	db_user=$(whiptail --inputbox "Database user:" 8 78 "sqlman" --title "Query Dialog" 3>&1 1>&2 2>&3)
	db_pass=$(whiptail --passwordbox "Database pass:" 8 78 "dbs1721" --title "Query Dialog" 3>&1 1>&2 2>&3)
	MYSQL="/usr/bin/mysql --batch --skip-column-names --max_allowed_packet=100M -h${db_host} --port=${db_port} -u${db_user} -p${db_pass} -e"
}

blank

The next example connects to the database to populate a whiptail radio checklist with the names of available schemas. The second function connects to the selected database to get a list of tables and build another radio checklist. This is a good example of how to build dynamic menus.

get_db_name() {
	db_name=$(whiptail --title "Available Databases" --radiolist "Selects a database:" 30 78 20 \
	`$MYSQL "show databases;" | sed 's/$/ DB OFF/g'` 3>&1 1>&2 2>&3)
	MYSQL="/usr/bin/mysql --batch --skip-column-names --max_allowed_packet=100M -h${db_host} --port=${db_port} -u${db_user} -p${db_pass} ${db_name} -e"
}

get_tbl_name() {
	tbl_name=$(whiptail --title "Available Tables in ${db_name}" --radiolist "Selects a table:" 30 78 20 \
	`$MYSQL "show tables;" | sed 's/$/ TBL OFF/g'` 3>&1 1>&2 2>&3)
}

The whiptail utility has an unfortunate limitation: it can only ask you one question at a time. This can get tedious and distracting. The following example uses dialog to present you with three questions and set variables var1 through var3. The dialog can’t assign each response to an individual variable. You would need to parse the output as illustrated below.

dialog_do() {
  # Clear variables var1 - var3
  unset var{1..3}

  # Set default values
  var1="default value 1"
  var2="default value 2"
  var3="default value 3"

  # Launch dialog and gather user input
  exec 3>&1
  dialog_values="$(dialog --ok-label "Submit" \
  --clear \
  --output-separator : \
  --backtitle "Setting variables var1 - var7" \
  --title "Some questions for you" \
  --form "Answer this" \
  20 60 0 \
  "Enter var1:" 1 1 "${var1}" 1 16 25 0 \
  "Enter var2:" 2 1 "${var2}" 2 16 25 0 \
  "Enter var3:" 3 1 "${var3}" 3 16 25 0 \
  2>&1 1>&3)"
  exec 3>&-
  clear

  # Now we parse the input assigned to colon-separated ${dialog_values} and set
  # individual variables
  for i in {1..3}; do
    eval "$(echo var${i})"="\"$(awk -F: -v i="${i}" '{print $i}' <<<${dialog_values})\""
  done

  # And this is just to show that the variables have been set
  for i in {1..3}; do
    eval echo $(echo $`eval echo "var${i}"`)
  done
}

blank

And a few simple whiptail dialog boxes that may come in handy in many scripts. This is a simple yes/no dialog:

if (whiptail --title "Continue/Exit" --yes-button "Continue" --no-button "Exit"  --yesno "Proceed with the build?" 20 60); then
echo "Continuing"
fi

blank

Here’s a simple message box. For some visual interest I added output of an array containing output of the ps command:

IFS=$'\n'; a=($(ps -eLfw)); unset IFS
whiptail --title "Simple message box" --msgbox "This is first line\n\nThis is second line\n\n$(for ((i = 0; i < ${#a[@]}; i++)) ; do echo "${a[$i]}" ; done | grep httpd)" 30 100

blank

This is another message box designed to hold a bit more content:

whiptail --title "Scrollbox" --scrolltext --msgbox "$(tail -1000 /var/log/messages)" 30 100

blank