[Chapter 10] 10.4 A Menu-Based Command Generator
Author: Dale Dougherty & Arnold Robbins
Date: 1998-08-03T21:01:42Z

#

| Chapter 10 The Bottom Drawer |

## 10.4 A Menu-Based Command Generator

In this section, we look at a general use of the `system()` and `getline` functions to implement a menu-based command generator. The object of this program is to give unsophisticated users a simple way to execute long or complex UNIX commands. A menu is used to prompt the user with a description of the task to be performed, allowing the user to choose by number any selection of the menu to execute.

This program is designed as a kind of interpreter that reads from a file the descriptions that appear in the menu and the actual command lines that are executed. That way, multiple menu-command files can be used, and they can be easily modified by awk-less users without changing the program.

The format of a menu-command file contains the menu title as the first line in the file. Subsequent lines contain two fields: the first is the description of the action to be performed and the second is the command line that performs it. An example is shown below:

>     $ cat uucp_commands
>     UUCP Status Menu
>     Look at files in PUBDIR:find /var/spool/uucppublic -print
>     Look at recent status in LOGFILE:tail /var/spool/uucp/LOGFILE
>     Look for lock files:ls /var/spool/uucp/*.LCK

The first step in implementing the menu-based command generator is to read the menu-command file. We read the first line of this file and assign it to a variable named `title`. The rest of the lines contain two fields and are read into two arrays, one for the menu items and one for the commands to be executed. A `while` loop is used, along with `getline`, to read one line at a time from the file.

>     BEGIN { FS = ":"
>     if ((getline < CMDFILE) > 0)
>     	title = $1
>     else
>     	exit 1
>     while ((getline < CMDFILE) > 0) {
>     	# load array
>     	++sizeOfArray
>     	# array of menu items
>     	menu[sizeOfArray] = $1
>     	# array of commands associated with items
>     	command[sizeOfArray] = $2
>     	}
>     ...
>     }

Look carefully at the syntax of the expression tested by the `if` statement and the `while` loop.

>     (getline < CMDFILE) > 0

The variable `CMDFILE` is the name of the menu-command file, which is passed as a command-line parameter. The two angle-bracket symbols have completely different functions. The "<" symbol is interpreted by `getline` as the input redirection operator. Then the value returned by `getline` is tested to see if it is greater than (">") 0. It is parenthesized on purpose, in order to make this clear. In other words, "getline < `CMDFILE`" is evaluated first and then its return value is compared to 0.

This procedure is placed in the `BEGIN` pattern. However, there is one catch. Because we intended to pass the name of the menu file as a command-line parameter, the variable `CMDFILE` would not normally be defined and available in the `BEGIN` pattern. In other words, the following command will not work:

> `awk` `script` `CMDFILE="uucp_commands"` `-`

because `CMDFILE` variable won't be defined until the first line of input is read.

Fortunately, awk provides the -v option to handle just such a case. Using the -v option makes sure that the variable is set immediately and thus available in the `BEGIN` pattern.

> `awk -v CMDFILE="uucp_commands"` `script`

If your version of awk doesn't have the -v option, you can pass the value of `CMDFILE` as a shell variable. Create a shell script to execute awk and in it define `CMDFILE`. Then change the line that reads `CMDFILE` in the `invoke` script (see below) as follows:

>     while ((getline < '"$CMDFILE"') > 0 ) {

Once the menu-command file is loaded, the program must display the menu and prompt the user. This is implemented as a function because we need to call it in two places: from the `BEGIN` pattern to prompt the user initially, and after we have processed the user's response so that another choice can be made. Here's the `display_menu()` function:

>     function display_menu() {
>     	# clear screen -- comment out if clear does not work
>     	system("clear")
>     	# print title, list of items, exit item, and prompt
>     	print "\t" title
>     	for (i = 1; i <= sizeOfArray; ++i)
>     		printf "\t%d. %s\n", i, menu[i]
>     	printf "\t%d. Exit\n", i
>     	printf("Choose one: ")
>     }

The first thing we do is use the `system()` function to call a command to clear the screen. (On my system, `clear` does this; on others it may be `cls` or some other command. Comment out the line if you cannot find such a command.) Then we print the title and each of the items in a numbered list. The last item is always "Exit." Finally, we prompt the user for a choice.

The program will take standard input so that the user's answer to the prompt will be the first line of input. Our reading of the menu-command file was done within the program and not as part of the input stream. Thus, the main procedure of the program is to respond to the user's choice and execute a command. Here's that part of the program:

>     # Applies the user response to prompt
>     {
>        # test value of user response
>        if ($1 > 0 && $1 <= sizeOfArray) {
>     	# print command that is executed
>     	printf("Executing ... %s\n", command[$1])
>     	# then execute it.
>     	system(command[$1])
>     	printf("<Press RETURN to continue>")
>      	# wait for input before displaying menu again
>     	getline
>        }
>        else
>     	exit
>        # re-display menu
>        display_menu()
>     }

First, we test the range of the user's response. If the response falls outside the range, we simply exit the program. If it is a valid response, then we retrieve the command from the array `command`, display it, and then execute it using the `system()` function. The user sees the result of the command on the screen followed by the message "<Press RETURN to continue>." The purpose of this message is to wait for the user to finish before clearing the screen and redisplaying the menu. The `getline` function causes the program to wait for a response. Note that we don't do anything with the response. The `display_menu()` function is called at the end of this procedure to redisplay the menu and prompt for another line of input.

Here's the `invoke` program in full:

>     awk -v CMDFILE="uucp_commands"  '# invoke -- menu-based
>                                       # command generator
>     # first line in CMDFILE is the title of the menu
>     # subsequent lines contain: $1 - Description;
>     # $2 Command to execute
>     BEGIN { FS = ":"
>     # process CMDFILE, reading items into menu array
>       if ((getline < CMDFILE) > 0)
>     	title = $1
>       else
>     	exit 1
>       while ((getline < CMDFILE) > 0) {
>     	# load array
>     	++sizeOfArray
>     	# array of menu items
>     	menu[sizeOfArray] = $1
>     	# array of commands associated with items
>     	command[sizeOfArray] = $2
>       }
>       # call function to display menu items and prompt
>       display_menu()
>     }
>     # Applies the user response to prompt
>     {
>        # test value of user response
>        if ($1 > 0 && $1 <= sizeOfArray) {
>     	# print command that is executed
>     	printf("Executing ... %s\n", command[$1])
>     	# then execute it.
>     	system(command[$1])
>     	printf("<Press RETURN to continue>")
>      	# wait for input before displaying menu again
>     	getline
>        }
>        else
>     	exit
>        # re-display menu
>        display_menu()
>     }
>     function display_menu() {
>     	# clear screen -- if clear does not work, try "cls"
>     	system("clear")
>     	# print title, list of items, exit item, and prompt
>     	print "\t" title
>     	for (i = 1; i <= sizeOfArray; ++i)
>     		printf "\t%d. %s\n", i, menu[i]
>     	printf "\t%d. Exit\n", i
>     	printf("Choose one: ")
>     }' -

When a user runs the program, the following output is displayed:

>     UUCP Status Menu
>             1. Look at files in PUBDIR
>             2. Look at recent status in LOGFILE
>             3. Look for lock files
>             4. Exit
>     Choose one:

The user is prompted to enter the number of a menu selection. Anything other than a number between 1 and 3 exits the menu. For instance, if the user enters "1" to see a list of files in uucp's public directory, then the following result is displayed on the screen:

>     Executing ...find /var/spool/uucppublic -print
>     /var/spool/uucppublic
>     /var/spool/uucppublic/dale
>     /var/spool/uucppublic/HyperBugs
>     <Press RETURN to continue>

When the user presses the RETURN key, the menu is redisplayed on the screen. The user can quit from the program by choosing "4".

This program is really a shell for executing commands. Any sequence of commands (even other awk programs) can be executed by modifying the menu-command file. In other words, the part of the program that might change the most is extracted from the program itself and maintained in a separate file. This allows the menu list to be changed and extended very easily by a nontechnical user.

10.3 The system() Function |  | 10.5 Directing Output to Files and Pipes