Monday, March 1, 2010

dialog

Now that we have covered launching a script from the GUI, it's time to look at how to make our scripts a little more "graphical."  Interestingly, there are a number of tools that we can use to produce user interfaces for shell scripts.  In this installment, we will concentrate on the most mature of these tools, dialog.

The dialog program lets scripts display dialog boxes for user interaction.  It does this in a text-based manner, using the ncurses library.  It can produce a variety of user interface elements and can pass user input back to a script for processing.  The dialog package is not installed by default on most Linux distributions, but is readily available in most distribution repositories.

dialog has been around for a long time and has gone through a number of incarnations.  The current version (which is maintained by the Debian project) contains a number of features not found in earlier versions.

To demonstrate a few of the powers of dialog, we will write a script that displays a series of dialog boxes and provide some ideas of how to work with the output.  We'll start the script with the following code:


#!/bin/bash

# dialog-demo: script to demonstrate the dialog utility

BACKTITLE="Dialog Demonstration"

# message box

dialog  --title "Message Box" \
        --backtitle "$BACKTITLE" \
        --msgbox "This is a message box which simply\
        displays some text and an OK button." \
        9 50

The script invokes the dialog program with a series of options to set the title for the dialog box, the title for the background screen, the dialog box type (in this example a message box), the text to appear in the box and the size of the box (9 lines high by 50 characters wide).  When we run the script we get the following results:



Next we'll add the following code to the end of the script to generate another kind of dialog called a yes/no box.  In addition to the dialog box itself, we will also include some code to act on the results of the user's selection:

# yes/no box

dialog  --title "Yes/No Box" \
        --backtitle "$BACKTITLE" \
        --yesno "This is a yes/no box.  It has two buttons." \
        9 50

# examine the exit status and act on it
case $? in
        0)      dialog --title "Yes" \
                --backtitle "$BACKTITLE" \
                --msgbox "You answered \"Yes\"" \
                9 50
                ;;
        1)      dialog --title "No" \
                --backtitle "$BACKTITLE" \
                --msgbox "You answered \"No\"" \
                9 50
                ;;
        255)    dialog --title "Esc" \
                --backtitle "$BACKTITLE" \
                --msgbox "You pressed Esc" \
                9 50
                ;;
esac

The yes/no box offers the user three choices: yes, no, and a cancel which occurs if the Esc key is pressed.  To select a button, the user my use the Tab key to switch from button to button or, if dialog is being run inside a terminal emulator on a graphical desktop, the mouse may be used to select a button.

dialog communicates the user's choice back to the script via its exit status.  In the code that follows the yes/no box, we evaluate the exit status.  If the yes button is pressed, the exit status is 0, if the no button is pressed, the exit status is 1, and if Esc is pressed, the exit status is 255.




dialog can provide more than just simple button presses.  It can support edit boxes. forms, menus, file selectors. etc.  When returning more complex data, dialog outputs its results on standard error.  To demonstrate how this works, we will insert the following code near the beginning of the script (just after the BACKTITLE=... line) to define a function (read_tempfile) that will display the dialog output:


TEMPFILE=/tmp/dialog-demo.$$

read_tempfile() {

# display what is returned in the temp file

        local tempfile

        # read file contents then delete
        tempfile=$(cat $TEMPFILE)
        rm $TEMPFILE

        # message box to display contents
        dialog --title "Tempfile Contents" \
                --backtitle "$BACKTITLE" \
                --msgbox "The temp file contains: $tempfile" \
                0 0
}

Next, we will add the following code to the end of our script to display a menu box:

# menu

dialog --title "Menu" \
        --backtitle "$BACKTITLE" \
        --menu "This is a menu.  Please select one of the following:" \
        15 50 10 \
        1 "Item number 1" \
        2 "Item number 2" \
        3 "Item number 3" 2> $TEMPFILE

read_tempfile

This code displays a menu containing three items.  Each item consists of two elements; a "tag" which is returned when a choice is made and an "item string" which describes the menu selection.  In the example code above, we see that the first menu item has the tag "1" and the item string "Item number 1."  When we execute the code, dialog displays the following menu:



To capture and display the output of the menu, we redirect the standard error of dialog to the file named in the constant TEMPFILE.  The read_tempfile function assigns the contents of the temporary file to a variable (tempfile) and passes it to dialog to display in a message box.  After the menu is displayed and a selection is made by the user, the message box appears and displays the data returned to the script.

Next, we will add a checklist dialog by adding the following code to the end of the script.


# checklist

dialog --title "Checklist" \
        --backtitle "$BACKTITLE" \
        --checklist "This is a checklist.  Please from the following:" \
        15 50 10 \
        1 "Item number 1" off \
        2 "Item number 2" off \
        3 "Item number 3" off 2> $TEMPFILE

read_tempfile

As we can see, this code is almost the same as the code used to create the menu dialog.  The main difference is that the items in the checklist add a third field called "status."  The value of status may be either "on" or "off" and will determine if a selection is already selected or deselected when the program is run:




The user may select one or more checkbox items by pressing the space bar.

Next, we will add the following code to the end of the script to display a file selector
:

# file selector

dialog  --title "File Selector" \
        --backtitle "$BACKTITLE" \
        --fselect ~/ 10 30 2> $TEMPFILE

read_tempfile

For the file selector, dialog is passed the base directory for the selection (in this example the directory ~/) and the size of selector.  For this example we specify 10 files in the list and a 30 character wide dialog.




I think the file selector is easy to code, but awkward to use.  You move around the dialog with the Tab key and select directories and files with the space bar.

One of the more novel types of boxes provided by dialog is the "gauge" which displays a progress bar.  To try out this feature, we will add this code to the end of the script:


# gauge

# generate a stream of integers (percent) and pipe into dialog

percent=0

while (( percent < 101 )); do
        echo $percent
        sleep 1

        percent=$((percent + 10))
done | dialog --title "Gauge" \
        --backtitle "$BACKTITLE" \
        --gauge "This is a gauge.  It shows a progress bar." \
        0 0

The gauge dialog does not output anything, rather it accepts a stream of integers (representing the percent of completion) via standard input.  To provide this, we create a while loop to generate the stream of integers and pipe the results of the loop into dialog.  When the script runs, the stream of numbers causes the progress bar to advance:



Note too, that we specified the size of the dialog as 0 0.  When this is done, dialog attempts to auto-size the box to fit in the minimum necessary space.

Finally, we'll add another of the novel dialogs, a calendar:


# calendar

dialog  --title "Calendar" \
        --backtitle "$BACKTITLE" \
        --calendar "This is a calendar." \
        0 0 \
        0 0 0 2> $TEMPFILE

read_tempfile
The calendar dialog box is given a date in day month year format.  If the year is specified as zero, as it is in this example, the current date is used.  The user can then select a date and the date is returned to the script.




This concludes our brief look at dialog.  As you can imagine, dialog can be very handy for adding  improved user interfaces for those scripts that require it.  While we have covered its general themes, dialog has many more options and features.  Check out the documentation link below for the complete story.

Further Reading

Documentation for dialog:

Other dialog-like programs:

No comments:

Post a Comment