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.
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.