To learn and understand the dialog and whiptail commands to use them in shell scripts for fun and profit.
Once upon a time there was a program called dialog which was used to display text-based widgets in shell scripts. Over time, this software evolved and expanded, seeing code, license, and feature changes.
At some point, Whiptail emerged onto the scene. Whiptail is a dialog-like clone which aims to be lighter weight and makes use of the newt library (vs. the ncurses library which dialog uses).
It's easy to think that the shell interlace is boring and text-y, but you are wrong! Using the menu command from shell provides graphical-goodness to all of your shell scripts.
We've all made menus for programs made in shell, and as hard as you try this is about as fancy as you can get.
With one easy command, you can have menus like this!
This is actually much easier than you might think - though it will take a bit more code in your shell script to make it functional.
To draw this menu, the command line that I used was:
lab46:~$ dialog --menu "Fancy Menu 2" 50 50 100 "1)" "Item 1" "2)" "Item 2" "3)" "Item 3"
Pretty simple right? By typing “dialog” by itself, presented with all of the options available for you. Menu is just the beginning.
--menu <text> <height> <width> <menu height> <tag1> <item1>...
Taking a look at the menu implementation you can see that we are presented the options that the command will take as an argument.
<text> - This is the header of the menu box, a title. In my example it is “Fancy Menu 2”
<height> - The height of the menu box - 50 in our case
<width> - The width of the menu box - again, 50 in our case
<tag1> - This is where you choose what the user enters (and the input you will receive into your program). It could be “A)” “1)” or anything else thats intuitive.
<item1> - The description of the menu item. “New Game”, “Launch Missiles”, or whatever makes sense to your program.
We are not just limited to menu applications. A list below gives you an idea of other useful implementations.
--calendar <text> <height> <width> <day> <month> <year> --checklist <text> <height> <width> <list height> <tag1> <item1> <status1>... --dselect <directory> <height> <width> --editbox <file> <height> <width> --form <text> <height> <width> <form height> <label1> <l_y1> <l_x1> <item1> <i_y1> <i_x1> <flen1> <ilen1>... --fselect <filepath> <height> <width> --gauge <text> <height> <width> [<percent>] --infobox <text> <height> <width> --inputbox <text> <height> <width> [<init>] --inputmenu <text> <height> <width> <menu height> <tag1> <item1>... --menu <text> <height> <width> <menu height> <tag1> <item1>... --mixedform <text> <height> <width> <form height> <label1> <l_y1> <l_x1> <item1> <i_y1> <i_x1> <flen1> <ilen1> <itype>... --mixedgauge <text> <height> <width> <percent> <tag1> <item1>... --msgbox <text> <height> <width> --passwordbox <text> <height> <width> [<init>] --passwordform <text> <height> <width> <form height> <label1> <l_y1> <l_x1> <item1> <i_y1> <i_x1> <flen1> <ilen1>... --pause <text> <height> <width> <seconds> --progressbox <height> <width> --radiolist <text> <height> <width> <list height> <tag1> <item1> <status1>... --tailbox <file> <height> <width> --tailboxbg <file> <height> <width> --textbox <file> <height> <width> --timebox <text> <height> <width> <hour> <minute> <second> --yesno <text> <height> <width>
You can further change the look of dialogs through the options supplied as arguments. Available options are shown below.
[--ascii-lines] [--aspect <ratio>] [--backtitle <backtitle>] [--begin <y> <x>] [--cancel-label <str>] [--clear] [--colors] [--column-separator <str>] [--cr-wrap] [--date-format <str>] [--default-item <str>] [--defaultno] [--exit-label <str>] [--extra-button] [--extra-label <str>] [--help-button] [--help-label <str>] [--help-status] [--ignore] [--input-fd <fd>] [--insecure] [--item-help] [--keep-tite] [--keep-window] [--max-input <n>] [--no-cancel] [--no-collapse] [--no-kill] [--no-label <str>] [--no-lines] [--no-ok] [--no-shadow] [--nook] [--ok-label <str>] [--output-fd <fd>] [--output-separator <str>] [--print-maxsize] [--print-size] [--print-version] [--quoted] [--scrollbar] [--separate-output] [--separate-widget <str>] [--shadow] [--single-quoted] [--size-err] [--sleep <secs>] [--stderr] [--stdout] [--tab-correct] [--tab-len <n>] [--time-format <str>] [--timeout <secs>] [--title <title>] [--trace <file>] [--trim] [--version] [--visit-items] [--yes-label <str>]
Dialog/Whiptail also possess the ability to display impressive-looking progress bars, for use in installing a program or processing a complex task. While amazing, its implementation is a little challenging to the uninitiated.
According to the whiptail(1) manual page:
--gauge text height width percent A gauge box displays a meter along the bottom of the box. The meter indicates a percentage. New percentages are read from standard input, one integer per line. The meter is updated to reflect each new percent- age. If stdin is XXX, then subsequent lines up to another XXX are used for a new prompt. The gauge exits when EOF is reached on stdin.
The trick here is to notice that gauge boxes work in conjunction with STDIN. That is, they work because you are feeding them information. No information, no progress bar.
It also literally means having “XXX” being output. That isn't some crazy talk for wildcards… it really wants “XXX”, your prompt updating output, then another “XXX”.
This can lead to very disappointing early results, and it can take some searching to find some actual examples demonstrating how to use this functionality.
Here are some working examples to try:
This can be classified as a “nifty but does nothing” example… but can open the door to better understanding how this functionality works.
We'll simply count to 100 in a loop, have a small delay within, and have whiptail work its magic:
for((i=1; i<=100; i++)); do echo "$i" sleep 0.24 done | whiptail --backtitle "Demonstrating the gauge box" --title "Progress" --gauge "Processing ..." 6 70 0
Where the gauge can really be useful is in processing files or lines within files. We can make use of the prompt update capabilities and get a really impressive output.
This example will “process” the files in /dev, updating the prompt while advancing the progress bar towards completion:
cd /dev cnt="`/bin/ls -1Ad * | wc -l`" pct="`echo \"100/$cnt\" | bc -l`" total=0 for file in `/bin/ls -1Ad *`; do total=`echo $total+$pct | bc -l` echo $total|cut -d'.' -f1 sleep 0.08 echo "XXX" echo "Processing ... $file" echo "XXX" done | dialog --backtitle "Demonstrating the gauge box in dialog" --title "Progress" --gauge "About to process /dev ..." 6 70 0
This is an area where the behavior of dialog and whiptail diverge. While you can substitute 'whiptail' in for 'dialog', you will not get the exact same result. Dialog seems to work especially well in this example.
Ideally you'd only want to put a gauge on a time-consuming process, negating any need for an artificially delaying sleep, as has been used in these examples.
Of particular note to whiptail and gauge boxes, the following statement proved insightful:
It appears that whiptail(1) writes its control output to the termininal based on the setting of the TERM environment variable. Conseqently, you can't use the standard output stream of whiptail(1) to set a variable. Also, whiptail(1) writes the user-input of the input box to the standard error stream so, again, you can't use its standard output stream to set a variable.