|
|
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() doneThe 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 quitThis 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 0The 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).