Many novice Linux users would have many less problems in using the command line if, in any moment, they only had to make choices within a limited, predefined set of alternatives offered by the script they are using.
Such interfaces would also help everybody, including veteran administrators, to make less errors. Shell scripts can behave in this way, if they are written properly. Writing such scripts from scratch, however, often takes too much time to be worth it.
The most popular solution to this kind of problem are the Gtk graphic widgets provided by Zenity. I, too, use Zenity, and plan to write about it in future posts. Sometimes, however, Zenity isn’t an option: What if you want to quickly write an interactive shell script that also runs without X Windows, maybe over standard or Web-based SSH? Such cases are not uncommon. Traditional servers, for example, normally don’t install any graphical package. These days there are also plenty of FOSS-based set-top boxes, storage appliances and other embedded devices that offer a console and a shell, but no X Windows. Sure, many of those objects have some Web administration interface, but wouldn’t it be nice to add interactive scripts to them?
A way to quickly write such scripts is to use whiptail. As you can see in Figure A, whiptail produces the same kind of dialogs and menus normally found in the text installation procedures of many Linux distributions. Besides running entirely inside a terminal, whiptail has a few less features than Zenity. The two most important missing widgets are the graphical file selector and the calendar. Whiptail, however, has all the other, most common interfaces you may need in a shell script. The man page of whiptail is quite complete except, in my opinion, for one gap that I will try to fill here. Namely, the whiptail documentation describes all the available widgets and all their options, but it is too synthetic when it comes to where the output of whiptail goes, and how to retrieve it.
Figure A
Almost all whiptail functions generate a binary output, to tell the calling script which of two buttons (usually “OK” and “Cancel”) the user pressed. Whiptail always encodes this information in its exit status, which is available in the special shell variable $?. As an example, the widget in Figure A comes from this piece of code:
#! /bin/bash
whiptail --yesno "Did you already know whiptail?" --yes-button "Yes, I did" --no-button "No, never heard of it" 10 70
CHOICEs=$?
When you run that code, $CHOICE becomes 0 when you answer ,”Yes, I did”, 1 if you press “No…” and -1 if some error occurred.
As I said, this is the base behavior of whiptail. Several functions, however, must also pass to the script some text that the user typed or selected. The man page does say that such text is written to the standard error, but stops there, without describing how to retrieve it in practice. It is possible, as explained in this example, to directly save that text in a shell variable. Personally, I very much prefer to write everything to a temporary file: this approach leaves traces of how the script worked in case of problems or crashes and makes it easier both to parse the output and to understand the code. To see what I mean and how to do it, please look at the whiptail command that generates the menu of Figure B:
whiptail --menu "Preferred Linux Distro" 10 40 4 \
debian "Venerable Debian" off \
ubuntu "Popular Ubuntu" on \
fedora "Hackish Fedora" off \
centos "Stable Centos" off \
mint "Rising Star Mint" off 2>distrochoice
Figure B
The trick is in the last line. In Unix, the numeric descriptor for the standard error of a program has 2 as default value 2. Therefore, adding 2>distrochoice to the whiptail invocation redirects its standard error to the distrochoice file. That is where you will find the tag (debian in the case of Figure B) corresponding to the selected voice, while $? will still work as I already explained.
Should you need to retrieve multiple choices, as shown in Figure C, you should use the –checklist function:
whiptail --checklist "Preferred Linux distros" 15 40 5\
debian "Venerable Debian" off \
ubuntu "Popular Ubuntu" on \
fedora "Hackish Fedora" off \
centos "Stable Centos" off \
mint "Rising Star Mint" off 2>distrochoice
Figure C
In this case there is one more thing to know. By default, whiptail will write everything that was selected as a continuous, space-separated list of quoted values:
marco:> cat distrochoice
"ubuntu" "centos"
The alternative is to add the –separate-output switch as first option. Doing so will tell whiptail to write one selected option per line, without quotes:
marco:> cat distrochoice
ubuntu
centos
Of course, there is no way that is better than the other. It all depends on what kind of processing you need to do with the data that whiptail collected. In any case, saving everything to a file will give you all the flexibility that whiptail can offer.