Shell programming and simple menus - part 1

Unix Insider –

Any programmer or system administrator can benefit from limiting end-user access to the things that they actually need. For the timid, Unix offers little help on how to start, and for the adventurous Unix offers too many simple commands that can cause havoc.

A menu system is an almost mandatory add-on to any Unix system in the real world. In saying "real world" Unix system, I mean one that doesn't involve sitting in a dark room hunched over a glowing terminal trying to determine the next highest prime number.

Commercial menu systems involve more than a simple prompt that dispatches to some program based on a user selection. But often a simple approach will get the job done. While I was creating EZMENU for Unix, one customer required just such a simple solution.

The menu had to:

  1. Provide one or more prompts.
  2. Allow the user to select a prompt.
  3. Execute one or more commands associated with the selection.

The menu also had to provide a method for the user to exit the menu.

The design of the menu is simple, and it provides an excellent vehicle to learn some shell programming tricks. The logic for a menu can be described in the following simple steps:

  1. Display a menu.
  2. Get the user's menu pick.
  3. Check that the pick is valid and if not, display an error message and return to step 1.
  4. If the user's menu pick is to exit, then exit.
  5. Otherwise, execute the command(s) associated with the menu pick.
  6. Loop back to step 1.

The menu prompts can be displayed directly using the echo command as in:

<font face="Courier">echo "Please enter 1 to select Accounts Receivable"</font>

or indirectly using a variable as in:

<font face="Courier">amenu="Please enter 1 to select Accounts Receivable"
echo $amenu
</font>

A menu pick can be collected from the user by using the read command which will read input from the keyboard as in:

<font face="Courier">echo "Please enter your choice"
read answer
 </font>

Defining functions

Another important feature of the shell (sh or ksh) is the ability to define a function. A shell function is a collection of one or more shell commands that are given a function name. A function can be executed by issuing the name of the function in the shell script as if it were a Unix command. However, a function must be defined before it can be executed. To define a function, give it a name followed by an open and close parentheses, followed by opening and closing curly braces. Within the curly braces place the commands to be executed.

The following lines define a function

<font face="Courier">pagedir</font>
and then execute it. The function executes an
<font face="Courier"> ls -l</font>
command and then pipes the results through
<font face="Courier">more</font>
to create a paging display.

<font face="Courier">#!/bin/sh
# Define a paged directory listing function
pagedir () { ls -l|more ; }
# and then execute it
pagedir
</font>

Repeating a command or set of commands can be done by enclosing the commands within a while-do-done command.

In the following example the shell logic loops until the user enters something other than "y."

<font face="Courier">#!/bin/sh
answer=y
while [ $answer = y ]
do
    echo "Hello"
    echo "go again?"
    read answer
done
</font>

Shell scripts also allow for a case-select type construction,

<font face="Courier">case-esac</font>
that executes one of several choices based on a variable containing one of a list of values. In the example below the choices are a), b), c), and finally, *), which stands for anything else.

<font face="Courier">#!/bin/sh
# How to use case-esac
echo "Please enter a letter a through c"
read answer
case $answer in
    a)    echo You chose a;;
    b)    echo You chose b;;
    c)    echo You chose c;;
    *)    echo You did not chose a, b or c;;
esac
</font>

A

<font face="Courier">case-esac</font>
choice can also be set up to allow the user to enter the upper or lower case versions. In the example below the choices are a|A), b|B), c|C), or *) which stand for a or A (a|A), b or B (b|B), c or C (c|C), or anything else.

<font face="Courier"># How to use case-esac with upper or lower case.
echo "Please enter a letter a through c"
read answer
case $answer in
    a|A)    echo You chose a;;
    b|B)    echo You chose b;;
    c|C)    echo You chose c;;
      *)    echo You did not chose a, b or c;;
esac
</font>

This set of tools can be used to build a simple shell script menu such as that shown in Listing 1 (below). Listing 1 contains line numbers to the left of each actual line in order to facilitate the explanation of the listing.

This menu allows other programs or shell commands to be launched directly from the menu. As further items are needed for the menu, a prompt can be added as

<font face="Courier">emenu</font>
, and the program name or commands can be placed inside the function definition of
<font face="Courier">epick ()</font>
. The required commands should replace
<font face="Courier"> badchoice ()</font>
as the routines to execute.

I chose .mnu as an extension for this script file to indicate that it was a menu, but there is no need for the extension.

Listing 1: Sample shell script menu

<font face="Courier">1.  #!/bin/sh
2.  # sample.mnu
3.  # A simple script menu under Unix
4.  # Main logic starts at MAIN LOGIC
5.  # The logo will be displayed at the top of the screen
6.  LOGO="Sample Menu"
7. 
8.  #------------------------------------------------------
9.  # MENU PROMPTS
10. #------------------------------------------------------
11. # A list of menu prompts to be displayed for the user.
12. # The list can be modified.
13. # In this first list, enter the menu prompt as it should appear
14. # on the screen for each of the letters A - L. In this example
15. # menu pick variables emenu through lmenu are blank as there
16. # are no menu selections for keys E through L.
17. 
18. amenu="a.  Job Scheduling"                ;
19. bmenu="b.  Set Standard Defaults "        ; 
20. cmenu="c.  Display Directory Listing "    ; 
21. dmenu="d   Payroll Menu "                 ;
22. emenu=" "                                 ;
23. fmenu=" "                                 ;
24. gmenu=" "                                 ;
25. hmenu=" "                                 ;
26. imenu=" "                                 ;
27. jmenu=" "                                 ;
28. kmenu=" "                                 ;
29. lmenu=" "                                 ;
30. 
31. #------------------------------------------------------
32. # MENU FUNCTION DEFINITIONS
33. #------------------------------------------------------
34.  
35. # Define a function for invalid menu picks
36. # The function loads an error message into a variable
37. badchoice () { MSG="Invalid Selection ... Please Try Again" ; } 
38.  
39. # For each prompt displayed above, there is a list of 
40. # commands to execute in response to the user picking the
41. # associated letter.
42. # They are defined as functions
43. # apick () through lpick () where
44. # apick () corresponds to the menu
45. # prompt amenu which is selected
46. # selected by pressing a or A.
47. # bpick () corresponds to the menu
48. # prompt bmenu which is selected by
49. # pressing b or B and so on.
50. # Any menu item that is not
51. # assigned a set of commands, is
52. # assigned
53. # the function badchoice () as a default for that pick.
54. # If the user
55. # selects a menu key that is assigned
56. # to badchoice (). This function
57. # causes an error message to be
58. # displayed on the screen.
59. # To add items to this second
60. # list, replace badchoice ()
61. # with the commands to run when
62. # that letter is pressed.
63. # The following steps simply define
64. # the functions, but do not cause
65. # any shell program steps to be executed.
66. 
67. apick () { sched ; }
68. bpick () { defmnt ; }
69. cpick () { ls -l| more ; echo Press Enter ; read DUMMY ; }
70. dpick () { payroll.mnu ; }
71. epick () { badchoice ; }
72. fpick () { badchoice ; }
73. gpick () { badchoice ; }
74. hpick () { badchoice ; }
75. ipick () { badchoice ; }
76. jpick () { badchoice ; }
77. kpick () { badchoice ; }
78. lpick () { badchoice ; }
79. 
80. #------------------------------------------------------
81. # DISPLAY FUNCTION DEFINITION
82. #------------------------------------------------------
83. # This function displays the menu.
84. # The routine clears the screen, echoes
85. # the logo and menu prompts
86. # and some additional messages.
87. # Note that this definition does
88. # not cause the function to
89. # be executed yet, it just defines
90. # it ready to be executed.
91. 
92. themenu () {
93. # clear the screen
94. clear
95. echo `date`
96. echo
97. echo "\t\t\t" $LOGO
98. echo
99. echo "\t\tPlease Select:"
100. echo
101. echo "\t\t\t" $amenu
102. echo "\t\t\t" $bmenu
103. echo "\t\t\t" $cmenu
104. echo "\t\t\t" $dmenu
105. echo "\t\t\t" $emenu
106. echo "\t\t\t" $fmenu
107. echo "\t\t\t" $gmenu
108. echo "\t\t\t" $hmenu
109. echo "\t\t\t" $imenu
110. echo "\t\t\t" $jmenu
111. echo "\t\t\t" $kmenu
112. echo "\t\t\t" $lmenu
113. echo "\t\t\tx. Exit"
114. echo
115. echo $MSG
116. echo
117. echo Select by pressing the letter and then ENTER ;
118. }
119.  
120. #------------------------------------------------------
121. # MAIN LOGIC
122. #------------------------------------------------------
123. # Every thing up to this point has been to define
124. # variables or functions.
125. # The program actually starts running here.
126. 
127. # Clear out the error message variable
128. MSG=
129. 
130. # Repeat the menu over and over
131. # Steps are:
132. # 1. Display the menu
133. # 2. 'read' a line of input from the key board
134. # 3. Clear the error message
135. # 4. Check the answer for a or A or b or B etc. and dispatch
136. #    to the appropriate program or function or exit
137. # 5. If the entry was invalid call the badchoice () function
138. #    to initialize MSG to an error message
139. # 6. This error message is used when setting up the menu
140. #    for a menu pick that is valid but has no command
141. #    associated with it.
142. 
143. while  true
144. do
145. # 1. display the menu
146.   themenu
147. 
148. # 2. read a line of input from the keyboard
149.   read answer
150. 
151. # 3. Clear any error message
152.   MSG=
153. 
154. # 4. Execute one of the defined functions based on the
155. #    letter entered by the user.
156. 
157. # 5. If the choice was E through L, the pre-defined
158. #    function for that pick will execute badchoice ()
159. #    which loads an error message into MSG  
160. 
161.   case $answer in
162.       a|A) apick;;
163.       b|B) bpick;;
164.       c|C) cpick;;
165.       d|D) dpick;;
166.       e|E) epick;;
167.       f|F) fpick;;
168.       g|G) gpick;;
169.       h|H) hpick;;
170.       i|I) ipick;;
171.       j|J) jpick;;
172.       k|K) kpick;;
173.       l|L) lpick;;
174. 
175. #      If the user selects =91x=92 to exit then break out
176. #      of this loop
177.       x|X) break;;
178.  
179. # 6. If the entry was invalid call the badchoice function
180. #    to initialize MSG to an error message
181.         *) badchoice;;
182.  
183.   esac
184. 
185. #     Do it again until the user enters =91x=92.
186. done
</font>

What the menu shell script does

Lines 1 through 30 contain the "data" for the menu system. Line 6 contains a menu name or logo that is displayed at the top of the menu. Lines 18 through 29 include the variables

<font face="Courier">amenu </font>
through
<font face="Courier">lmenu </font>
, containing the prompts for 12 lines of menu options. In Listing 1 prompt values are defined for
<font face="Courier">amenu</font>
through
<font face="Courier">dmenu</font>
. The variables
<font face="Courier"> emenu</font>
through
<font face="Courier">lmenu</font>
are left blank. To create your own custom menu you would modify these value to display up to 12 lines of menu options.

Lines 31 through 119 contain function definitions used by the script. In all of these lines of code, no actual program steps are performed. Instead the script builds the functions that will be used in the main logic of the program.

Line 35 contains a definition for a

<font face="Courier">badchoice ()</font>
function. This is a simple function that is used by the menu system whenever the user:

  1. Selects an invalid key. A through L, a through l, and X or x are the only valid keys.
  2. Selects a valid key that does not have a menu pick associated with it.

The

<font face="Courier">badchoice ()</font>
function is very simple. It loads a variable with an error message.

Lines 67 through 78 contain the definitions of 12 functions,

<font face="Courier">apick () </font>
through
<font face="Courier">lpick ()</font>
. These functions are the commands to be executed when a user makes a menu pick. For
<font face="Courier">epick</font>
through
<font face="Courier">lpick</font>
, the command is to call the
<font face="Courier">badchoice ()</font>
function.

The

<font face="Courier">apick ()</font>
function at line 67 starts the program, sched, a job scheduling program.

The

<font face="Courier">bpick ()</font>
function at line 68 starts defmnt, another program for maintaining some sort of defaults.

The

<font face="Courier">cpick ()</font>
function at line 69 invokes multiple commands,
<font face="Courier">ls -l| more</font>
followed by
<font face="Courier">echo Press Enter</font>
followed by
<font face="Courier">read DUMMY</font>
. These separate commands are all captured in a single line of semi-colon separated commands as:

<font face="Courier">{ls -l| more ; echo Press Enter ; read DUMMY ; }
</font>

The

<font face="Courier">dpick ()</font>
function at line 70 runs another menu script titled payroll.mnu.

These definitions of

<font face="Courier">apick ()</font>
through
<font face="Courier">lpick ()</font>
can be modified along with the prompts at lines 18 through 29 to create a custom menu. The remainder of the menu would be the same for all shell script menus.

At lines 92 through 118 a long function,

<font face="Courier">themenu ()</font>
, is defined. This function is used to display the menu on the screen. It clears the screen, echoes the date, the menu name ($LOGO), the 12 menu picks
<font face="Courier">amenu</font>
through
<font face="Courier">lmenu</font>
, an exit prompt, a $MSG variable, and a few other cosmetics to the screen.

So far nothing has actually happened in the program. To this point it consists of definitions of variables and functions to be used in the program.

The program itself appears at lines 120 through 186 at the end of the script beginning at MAIN LOGIC.

The program is one line of initialization of the MSG variable at line 128 and an infinite loop from lines 143 through 186.

Inside the loop the program steps are:

  1. Display the menu including all prompts and the MSG variable.
  2. "Read" an answer as a line of input from the key board.
  3. Clear the MSG variable.
  4. Check the answer for a or A through l or L and dispatch to the appropriate program or function.
  5. Check for x or X and exit.
  6. If the entry was invalid or for a menu pick that does not exist because it itself calls
    <font face="Courier">badchoice ()</font>
    , call the
    <font face="Courier">badchoice ()</font>
    function to initialize the MSG variable to an error message.
  7. Loop forever.

After you have created sample.mnu be sure to make it executable by typing:

<font face="Courier">chmod a+x sample.mnu 
</font>

The script displays a simple menu as in Figure 1 and asks the user to enter a single character and press enter to execute the request.

Figure 1 - The screen produced by sample.mnu

<font face="Courier">------------------------------------------------------------------------
Fri May 30 14:30:18 PDT 1997 

                        Sample Menu 

                Please Select: 

                        a.  Job Scheduling
                        b.  Set Standard Defaults                          
                        c.  Display Directory Listing
                        d   Payroll Menu







                        x. Exit 

Invalid Selection   Please try again 

Select by pressing the letter and then ENTER 

------------------------------------------------------------------------ 
</font>

If the user enters an invalid letter or selects a valid letter that has not yet had a selection attached to it, an

<font face="Courier">Invalid Selection</font>
message is displayed. An example of this message is already on the screen in line 21 as shown in Figure 1.

This provides a simple menu that can be maintained by editing the prompts at lines 18 through 29 and the picks at lines 67 through 78.

In the next installment we'll take this simple menu concept another few steps and learn some more about shell programming.

What’s wrong? The new clean desk test
Join the discussion
Be the first to comment on this article. Our Commenting Policies