Friday, 20 June 2008

Improve Bash Shell Scripts Using Dialog

Linux is based on the Unix operating system, but also features a number of unique and useful kernel features and application programs that often go beyond what is available under Unix. One little-known gem is “dialog”, a utility for creating professional-looking dialog boxes from within shell scripts. This article presents a tutorial introduction to the dialog utility, and shows examples of dialog box.

Linux is based on the Unix operating system, but also features a number of unique and useful kernel features and application programs that often go beyond what is available under Unix. One little-known gem is “dialog”, a utility for creating professional-looking dialog boxes from within shell scripts. This article presents a tutorial introduction to the dialog utility, and shows examples of how and where it can be used.


If you have installed a recent version of the Slackware Linux distribution, you've seen the professional-looking install process; it was created using the dialog utility.

True to the Unix tradition of writing general-purpose tools that work together, dialog allows creating text-based color dialog boxes from any shell script language. It supports eight types of dialogs:

  • yes/no boxes

  • menu boxes

  • input boxes

  • message boxes

  • text boxes

  • info boxes

  • checklist boxes

  • radiolist boxes

Dialog is very easy to use. If you've got your keyboard handy, here's a one-line example of a message box you can try. (Note: The examples in this article assume you are running a Bourne-compatible shell program such as GNU bash.)

% dialog --title 'Message' --msgbox 'Hello, world!' 5 20

This example creates a message box with the title “Message”, containing the greeting “Hello, world!”. The box is 5 lines high and 20 characters wide, with the message nicely centered in the box. An “OK” button appears at the bottom; pressing dismisses the menu.

Dialog Box Types

Most calls to dialog are in a similar format: an optional title, the dialog type, the text to be displayed, and the height and width (in characters) of the dialog box. Additional parameters specific to each menu type follow. Let's have a brief look at each of the available types.

The “yesno” menu is very similar to our first example:

% dialog --title "Message"  --yesno "Are you having\ fun?" 6 25

If you try this example, you will see that there are now two buttons at the bottom, labeled “Yes” and “No”. You can select between the buttons using the cursor keys (or ) and make your selection by pressing . The exit status returned to the shell will be 0 if “Yes” is chosen and 1 if a “No” selection is made.

You may wish to try experimenting with the height and width parameters. If the width is less than the string length, the string is wrapped around (at word boundaries). If you make the dialog box too small, then characters will be lost.

We previously saw the message box. The “infobox” is similar except that it does not wait for the user to select an “OK” button. This is useful for displaying a message while an operation is going on. Here is an example:

% dialog --infobox "Please wait" 10 30 ; sleep 4

The “inputbox” allows a user to enter a string. The usual editing keys can be used, and the text field scrolls if necessary. After the user enters the data, it is written to standard error (or more commonly redirected to a file as in this example):

% dialog --inputbox "Enter your name:" 8 40 2>answer

The “textbox” type is a simple file viewer; it takes a filename as a parameter:

% dialog --textbox /etc/profile 22 70

The usual movement keys work here: the cursor keys, Page Up, Page Down, Home, etc. You can exit by pressing or .

The “menu” type allows creating a menu of choices from which the user can choose. The format is

% dialog --menu   
[]

Each menu entry consists of a “tag” string and an associated “item” string, both of which are displayed. The user can make a choice using the cursor keys and pressing . The selected tag is written to standard error. Here is a simple example:

% dialog --menu "Choose one:" 10 30 3 1 red 2 green\ 3 blue

The next type is the “checklist”. The user is presented with a list of choices and can toggle each one on or off individually using the space bar:

% dialog --checklist "Choose toppings:" 10 40 3 \
1 Cheese on \
2 "Tomato Sauce" on \
3 Anchovies off

The third field in each choice is the initial state; -either “on” or “off”. The last type is the “radiolist”, essentially the same as the checklist except that the user must make one choice from a list of mutually exclusive options. The radiolist type, and the alternate form of title show here, were introduced in version 0.4 of dialog.

% dialog --backtitle "CPU Selection" \
--radiolist "Select CPU type:" 10 40 4 \
1 386SX off \
2 386DX on \
3 486SX off \
4 486DX off
A Real Application

The preceding examples were somewhat unrealistic; dialog is normally used within a shell script to do some real work. Let's look at a simple but useful application. I use the following script to back up my home directory to floppy disk on a regular basis:

#!/bin/sh
# Backup all files under home directory to a single # floppy
# Display message with option to cancel
dialog --title "Backup" --msgbox "Time for backup \ of home directory. \
Insert formatted 3-1/2\" floppy and press \ to start backup or \
to cancel." 10 50
# Return status of non-zero indicates cancel
if [ "$?" != "0" ]
then
dialog --title "Backup" --msgbox "Backup was \ canceled at your
request." 10 50
else
dialog --title "Backup" --infobox "Backup in \ process..." 10 50
cd ~
# Backup using tar; redirect any errors to a
# temporary file
# For multi-disk support, you can use the
# -M option to tar
tar -czf /dev/fd1 . >|/tmp/ERRORS$$ 2>&1
# zero status indicates backup was successful
if [ "$?" = "0" ]
then
dialog --title "Backup" --msgbox "Backup \
completed successfully." 10 50
# Mark script with current date and time
touch ~/.backup
else
# Backup failed, display error log
dialog --title "Backup" --msgbox "Backup failed \ -- Press

to see error log." 10 50
dialog --title "Error Log" --textbox /tmp/ERRORS$$ 22 72
fi
fi
rm -f /tmp/ERRORS$$
clear

To run this automatically, I put these lines in my .profile file to call the backup script on login if more than 3 days has elapsed since the last backup was made:

# do a backup if enough time has elapsed
find ~/.backup -mtime +3 -exec ~/.backup \;
A Longer Example

The sound driver for the Linux kernel uses a program called “configure” to prompt the user for sound configuration options. It generates a C header file based on the chosen options. A replacement based on dialog could offer some advantages, such as a more professional appearance and the ability to select options randomly from menus rather than as a linear sequence of questions.

Due to time and space constraints, I only present a partial (but functional) implementation of a sound driver configuration script. This could quite easily be extended to fully replace the current configure program.

The complete script is shown in as Listing 1. I'd like to explain it using a top down approach, which means reading the listing starting from the bottom.

The last part of the script is a while loop which simply calls the shell function main_menu repeatedly. Above that is the code to implement the main menu. We present the user with three choices, and redirect the selection to a file. One of three shell functions is then called, based on the user's choice.

The most important menu in this script is the next one, the config_menu function. Again we present the user with a number of choices. Note that in this case there is an option which returns the user back to the main menu.

Continuing to read our listing backwards, we come to the select_cards function. The kernel supports multiple sound cards, so here we use a checklist to present the user with the available choices. The command “on_off” is a utility function that will be shown later; it returns the string “on” if its parameters are equal, otherwise it returns “off”. This is the form that the checklist menu requires. Note that the return status of the command is checked. If the user selects “cancel” from the menu then the return status is non-zero and we return immediately without making any changes. Otherwise, we set appropriate variables to indicate which sound cards have been enabled.

The next function, as we read our listing backwards, it the function view_summary. This uses the textbox type to display a file containing information on the currently selected options. We first build up the data in the file before displaying it.

Our next function is select_dma. Here the user must make one of four mutually exclusive options, so we use the a radio list. If you try this example yourself, be aware that the radiolist type was added in dialog version 0.4; if you have an older version then you will have to make do with a checklist.

Availability

Up above, the routine select_irq uses very similar code to allow the user to select the final option in our configuration utility.

The purpose of this script is to generate a C language header file defining the compile options for the kernel sound driver. The “save” function does this. Notice how a dialog box is displayed while the save is in progress.

Above that we see the on_off function alluded to previously. This avoids some repetitive code in the script.

Finally, we see the clean_up routine which allows the user to exit from the script. At the top of the script some default values are defined for the configuration options and the temporary filename to use.

The configuration utility still needs a few enhancements to replace the existing program, including more kernel options and error checking, but the example does function and gives a feel for what can be done with dialog. I encourage you to type it in and try it.

Advanced Features

There are several more things that dialog can do. You can create and use a dialogrc file to customize the color and appearance of the dialog boxes. Dialog also supports displays that do not provide color or graphics characters. The details are given in the man page.

Dialog is “8-bit clean”, meaning that that international character sets other than the standard US ASCII are supported.

More Applications

For some longer examples of using dialog you can look at the sample scripts included with the dialog source code. Under Slackware Linux, the system configuration scripts can be found in /usr/lib/setup.

There are undoubtedly many possible uses for dialog. You could, for example, create a fully menu-driven interface for Linux users not familiar with shell commands. This could even be expanded into a simple bulletin board system that allowed users to read mail and Usenet news, edit files, etc.

The example sound driver script could be expanded into a tool for configuring all of the kernel compile options.

Incidently, dialog is reasonably portable and should run with minimal changes on any Unix-compatible system that has a curses library. It can also be used from any shell script language.

Conclusions

Dialog is a simple yet powerful utility, true to the Unix tradition of making each tool do one thing well. It can add a polished look to your applications and make them easier to use.

No comments: