Create menus and dialogs for shell scripts

Slashdot it! Delicious Share on Facebook Tweet! Digg!
Christopher Hall, Fotolia

Christopher Hall, Fotolia

Greater Comfort

From simple queries to complex menus: Using dialog, you can create a graphical interface for shell scripts with only a few extra lines of code.

The shell by itself is already an application programming interface, but few users still query data from a command line. To adapt to the habits of current users, the dialog tool simulates the elements of a graphical interface.

The look may be a bit old-fashioned but, in terms of speed, the technology is hard to beat. If an X server is running on the system, you can provide users some additional comfort by relying on a dialog counterpart, such as Zenity or Gtkdialog (see the "Relatives" box).

Relatives

Apart from the dialog framework described in this article, other projects exist with the same purpose. In Table 1, you can see to what extent other candidates provide dialog types. Some require a graphical user interface, among them Xdialog [3], Zenity [4], Kdialog [5], and Gtkdialog [6]. The latter deviates somewhat from the usual pattern: You create the XML files with the instructions the program reads in during the call.

Table 1

SHC Options

Dialog Command Xdialog Zenity Kdialog
--yesno --yesno --question --yesno
--msgbox --msgbox --warning or --info --msgbox
--infobox --infobox --passivepopup
--textbox --textbox --textinfo --textbox
--tailbox --tailbox
--pause
--gauge --gauge --progress --progressbar
--form
--inputmenu --2inputsbox/--3inputsbox
--calendar --calendar --calendar --calendar
--timebox --timebox
--inputbox --inputbox --entry --inputbox
--editbox --editbox --textinfo --textinfobox
--dselect --dselect --file-selection --getexistingdirectory
--fselect --fselect --file-selection --getopenfilename/--getsavefilename
--checklist --checklist --list --checklist
--radiolist --radiolist --list --radiolistl
--menu --menubox --list --menu

Screens and Menus

To read user input, the built-in Bash read command is often used together with the echo command for simple strings. If you want to change the input prompt, enter the -p option with the new text (Listing 1).

Listing 1

Read User Input

01 #! /bin/sh
02 echo "---------------------------"
03 echo "echo -n \"Input: \";read a"
04 echo -n "Input: ";read a
05 echo "---------------------------"
06 echo "read -p \"Input: \" a"
07 read -p "Input: " a
08 echo "---------------------------"

The -n option on line 4 prevents a newline at the end of the echo statement, which makes the text appear directly to the right of the read prompt.

Quotes are escaped with backslashes so that they don't have any effect on the results. The two methods shown on line 4 and line 7 achieve the same results.

The option -i <text> together with -e (readline support) provides the user with an entry already at the prompt. This works a bit easier with the Readpreprompt [1] external program. For proper use, run the command in a subshell, as in Listing 2, line 3. The tool sends the result to standard output. This command can make it easier to create dialogs for database applications.

Listing 2

Using readpreprompt

01 #! /bin/sh
02 a="Old value"
03 a=$(readpreprompt "Input: " "$a")
04 echo $a

The built-in echo command has little effect on the outcome format. To accurately position numerical values in the dialog, the printf command borrowed from the C programming language can help. The command does the rounding for decimal numbers and takes the shell language settings into account.

The basic structure of the printf command is printf "%<format>" <data>. The most important instructions for formatting appear in Table 2. The command differentiates between period and comma as decimal separator. In a pinch, pass the variable LC_NUMERIC or LANG with set and unset inside the script.

Table 2

Printf Instructions for Formatting

%5.2f Floating-point with five digits before and two digits after the decimal separator
%.10s String with a maximum 10 characters
%X\n Hexadecimal in uppercase
%x\n Hexadecimal in lowercase
%#X\n \ Hexadecimal in uppercase with leading 0x
%i\n Integer
-r \ Loosens the security settings during compilation, so that binaries can run on other computers with the same operating system. Currently mandatory for Arch Linux.
-D Enables debug mode of the binary program, which can generate a lot of additional information.
-T Creates a program traceable by Strace or similar tool.
-A Shows a short info and quits SHC without compiling the script.

An even easier method is to shoehorn output data into the desired format with tr , which you can add right in the statement (Listing 3, Example 2). Always end the printf statement with a newline (\n ). In some cases, the command also allows tabbing. The examples in Listing 3 show the main functions, and Figure 1 shows the result.

Listing 3

Format Output Data

#! /bin/bash
# Sample values
a=987,455
b=987.455
c="A-long-word"
d=30
# Example 1
# Output floating-point rounded to 2 digits
printf "%5.2f\n" $b
# Example 2
# Output floating-point, comma converted to period decimal separator
printf "%5.2f\n" `echo $a | tr , . `
# Example 3
# Text output truncated to 10 characters
printf "%.10s\n" $c
# Example 4
# Numeric conversions at presentation:
# Integer, hexadecimal, octal
printf "%i %X %o\n" $d $d $d
# Example 5
# Output integer,
# Hexadecimal (lowercase) with leading "0x"
printf "%i  %#x\n" $d $d
Figure 2: A simple menu with numerical selections.

Note especially the string output. A space character is typically a separator, which makes every word on the line a separate variable value. In Listing 3, you would get a five-line output if you used space characters instead of the hyphens to separate the words in the c variable.

When creating menus, proceed as in Listing 4. If there are just a few menu items, use numbers to identify them, which makes keyboard entry easier. If the numbers get too high, use lowercase characters. The script handles the function variables as strings, which avoids error messages when entries don't match. Figure 2 shows the example in action.

Listing 4

Create a Menu for User Input

! /bin/sh
while true; do
  clear
  echo "(1) Function A"
  echo "(2) Function B"
  echo "(9) End"
  echo " "
  echo -n "Choose function: "; read f
  if [ "$f" = "1" ]; then
    echo "Function A";sleep 3
  elif [ "$f" = "2" ]; then
    echo "Function B";sleep 3
  elif [ "$f" = "9" ]; then
    exit
  fi
done
Figure 2: A simple menu with numerical selections.

The example in Listing 5 (Figure 3) shows a dialog to process data for address management. For practical application, the functions for retrieving and saving the data would be from and to a database.

Listing 5

Create a Dialog to Process Data

#! /bin/sh
# Address sample data
# Database access would be enabled at this point
a="Mr."
b="Tux Penguin"
c="Icy Rd.  123"
d="South Pole 10001"
while true; do
  clear
  echo "-----------------------------------------"
  echo "      Address Processing"
  echo "------+------------+---------------------"
  echo "F-No. |            |    Value"
  echo "  1   |     Title: | "$a
  echo "  2   |      Name: | "$b
  echo "  3   |    Street: | "$c
  echo "  4   | City/Code: | "$d
  echo "------+------------+---------------------"
  echo "actions: [F-No.]: change line, [s] save,"
  echo "[q] quit"
  echo -n "action: ";read wn
  if [ "$wn" = "1" ]; then
    a=$(readpreprompt "Line $wn: " "$a")
  elif [ "$wn" = "2" ]; then
    b=$(readpreprompt "Line $wn: " "$b")
  elif [ "$wn" = "3" ]; then
    c=$(readpreprompt "Line $wn: " "$c")
  elif [ "$wn" = "4" ]; then
    d=$(readpreprompt "Line $wn: " "$d")
  elif [ "$wn" = "s" ]; then
    echo "This would be written to the database"
    break
  elif [ "$wn" = "q" ]; then
    exit
  fi
done
echo "-----------------------------"
echo $a
echo $b
echo $c
echo $d
Figure 3: With just a few lines of shell code, you can create a dialog to process data from a database.

With Dialog

The dialog [2] program comes preinstalled on many current distributions. The instructions consist of commands for designing the dialog, for defining the type of dialog (e.g., message, progress, input, etc.), and for geometric specifications. At the beginning of the instructions, you specify the title and background title. The commands also might require geometric settings.

The times of manually setting the size of the dialog are thankfully a thing of the past. If you want to omit the height in row and the width in characters, simply enter 0 0 , which works for most instructions. The tool then automatically adjusts the dialog proportions.

The following code shows the command structure.

$ dialog --title "Title" --backtitle "Background Title" <more parameters> 0 0

The type instructions indicate what the lines of code should do. Do you want the user to read, enter, or choose something? Table 3 shows the possible options.

In the example, the data lines are numbers and the functions are characters. The script refreshes the dialog after each change.

Although the yes/no and message boxes await a reply action, the info box is purely informational and will time out with no reply, based on the sleep or some other external setting.

You can expand the yes/no box with another button:

--extra-button --extra-label ""

Clicking the button returns the value 3 . The second expansion possibility is the help button, --help-button , which doesn't require additional values and returns the value 2 . Thus, the yes/no box provides four possible return values.

The small script in Listing 6 shows the dialog settings in action and how to add properties. The properties in the first call are extended correspondingly. Before the --yesno , you'll find the additional parameter --ok-label "<text>".

Listing 6

Dialog Settings in Action

#!/bin/bash
#! /bin/sh
while true; do
    dialog --title "YES/NO BOX" --backtitle "BACKGROUND TITLE" \
           --help-button --extra-button --extra-label "EXTRA" \
           --ok-label "Agree" --yesno "QUERY TEXT" 0 0
    dialog --title "MESSAGE BOX" --backtitle "BACKGROUND TITLE" \
           --msgbox "Return value: $?" 0 0
    dialog --title "END SCRIPT" --backtitle "BACKGROUND TITLE" \
           --defaultno --yesno "End shell script?"  0 0
  if [ $? -eq 0 ]; then
    exit
  fi
done

The same applies to the "No" button (--no-label "<text>" ), "Yes" button (--yes-label "<text>" ), and "Help" button (--help-label "<text>" ). The --defaultno option in the last dialog statement sets the default selection to No .

For the gauge (progress) box, the percent value is piped to the dialog command (Listing 7). The element also displays units of an overall value.

Listing 7

Gauge Progress

#! /bin/sh
percent=0
while [ $percent -lt 100 ]; do
  # Set the percent value
  percent=$(echo "$percent + 10" | bc)
  # Gauge value is passed via pipe to dialog
  echo $percent | dialog --title "GAUGE" --backtitle "BACKGROUND TITLE" --gauge "PROGRESS" 0 0
  # Sleep only for demo!
  sleep 1
done

Listing 8 is a "ready to use" script. It shows the current size of the /home directory (Figure 4). Note this only works if /home is on its own partition, and the extraction of values with df can be a bit confusing. The percentage appears as a status bar, and the free space shows as clear text.

Listing 8

Show Current Size of /home

#! /bin/sh
# Percentage derived from df -h
PERCENT=$(df -h | grep "/home" | tr -s , , | cut -d' ,  -f5 | cut -d% -f1)
FREE=$(df -h | grep "/home" | tr -s , , | cut -d' , -f4)
echo ${PERCENT} | dialog --title "Size of /home directory" --backtitle "System Information" \
  --gauge "\n Current free space: ${FREE}B" 10 50
sleep 5
Figure 4: At a glance, you can see how full the /home partition is.

Buy this article as PDF

Express-Checkout as PDF

Pages: 8

Price $0.99
(incl. VAT)

Buy Ubuntu User

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content