|
|
Starting from our current example, it is not difficult to turn the script into a fully interactive program with menus. We have already seen most of the structures we need: all that is necessary is to put them together in a different order.
The general structure of a batch mode script is as follows:
Define constants (variables that will not change)
Define functions (routines to handle specific jobs)
Set traps
get command line options with getopts
use options to set control variables
for all in $
do
some_function()
done
The only element of repetition is the loop at the end, which repeats
for each file passed to the script as an argument.
A menu driven script behaves differently:
Initialize variables and define functions
Repeat (until some ``exit'' state is reached)
{
Display a menu
Get the user's choice
Do something with the choice (change state or call function)
}
On ``exit'' close files and quit
This process, an endless loop, is called a mainloop. The menu is
displayed, then a function like getc (described in
``Reading a single character from a file or a terminal'')
is used to retrieve a single keystroke. Such a function may either
grab the first key the user presses, or let them correct the entry
and press <Enter> before accepting input. (There are arguments for and
against both strategies. In general, you should always give your
users an opportunity to check their input, and correct any mistakes
they may have made.)
Depending on the value of the key, an option is selected from a case statement. Each option either sets a variable, or calls a function (called a callback) which does something in the background, ``behind'' the menu. Finally, if the option to quit is selected, the break statement is executed to quit the loop.
Here is part of a menu based script, containing the mainloop:
282 : done
283 : if [ $help = "yes" ]
284 : then
285 : _help
286 : exit 1
287 : fi
288 : if [ $batch = "yes" ]
289 : then
290 : analyze
291 : exit 0
292 : fi
293 : #
294 : #---------- enter the mainloop ------------------------
295 : #
296 : while :
297 : do
298 : echo $CLS
299 : echo "
300 :
301 : ${HILITE}Readability Analysis Program${NORMAL}
302 :
303 : Type the letter corresponding to your current task:
304 :
305 : f Select files to analyze [now ${HILITE}$fname${NORMAL} ]
306 : p Perform analyses
307 : l switch ${next_log_state} report logging [now ${HILITE}$log${NORMAL}]
308 : q quit program
309 :
310 :
311 : =======>"
312 : getc char
313 : case $char in
314 : 'f') getloop=1
315 : get_file ;;
316 : 'p') analyze
317 : strike_any_key ;;
318 : 'l') toggle_logging ;;
319 : 'q') break ;;
320 : *) continue ;;
321 : esac
322 : done
323 : clear
324 : exit 0
The first part of this extract, lines 283 to 292, check to see
whether help is to be printed, or the script is to be run in batch
mode: if the answer to the latter question is yes, a function called
analyze is called and the script exits without presenting
a menu. Then we see the mainloop, from line 284 to
324. $endloop is initially set to NO, so the
test at the top of the loop evaluates to true: therefore the body of
the do loop is executed at least once.
Within the loop, a menu is printed and then the script waits for the user to press a key. The character that is read is used to trigger a case statement (lines 312 to 321) that either modifies the state of some variables, or calls a function (like analyze, which does the analysis work, or getfile, which prompts the user for the name of a file to work on, or strike_any_key, which prints a message like ``Press any key to continue'').
Note the use of reverse video in the menu to emphasize important information. In general, you should try to make menu driven interfaces guide the user through to the next step in an intuitive and natural manner. One way of doing this is to highlight the important default information (like the file to be processed), in close proximity to the option that changes it (like the option to select a file to analyze).
Also worth noting is the use of ``toggle'' variables, that switch an additional feature on or off. The variables $log and $next_log_state perform this function for logging. They are switched within a separate function, toggle_logging:
83 : toggle_logging ()
84 : {
85 : log=$next_log_state
86 : case $log in
87 : ON) next_log_state=OFF ;;
88 : OFF) next_log_state=ON ;;
89 : esac
90 : }
log indicates whether output is to be logged to a file;
next_log_state is used in a message display that tells the
user whether they can switch logging on or off. (By definition,
next_log_state and log must be in opposite
states at all times.)
It is very easy for a mainloop to become too big to read. For this reason, any task that has more than one step is farmed out to another function. This includes the display of submenus. For example, get_file uses a menu to select a file to check:
145 : get_file()
146 : {
147 : while :
148 : do
149 : echo $CLS
150 : echo "
151 :
152 : ${HILITE}Select a file${NORMAL}
153 :
154 : Current file is: [${HILITE} $fname ${NORMAL}]
155 :
156 : Type the letter corresponding to your current task:
157 :
158 : [space] Enter a filename or pattern to use
159 : l List the current directory
160 : c Change current directory
161 : q quit back to main menu
162 :
163 :
164 : =======>"
165 : getc char
166 : case $char in
167 : ' ') get_fname ;;
168 : 'l') ls | ${PAGER:-more} ;;
169 : 'c') change_dir ;;
170 : 'q') break ;;
171 : *) ;;
172 : esac
173 : strike_any_key
174 : done
175 : }
This function contains a couple of features that do not appear in
the mainloop. Notably, it calls a routine for changing directory, a
routine for getting a filename, and lists the contents of a
directory (using the pager indicated by the environment variable
PAGER, or more if PAGER is not set).