Taking Shell Scripts Beyond Simple Batch Functionality
Emmett Dulaney
Shell scripts can be as simple or as complex as you
care to make them.
In their simplest form, they are nothing more than batch
files -
combining one or more single-line commands into one
short, executable
file. In their more complex form, they perform logic
operations and
manipulations in the interest of time saving and automation.
The key to increasing the functionality of your shell
scripts while
maintaining manageability is to know what external utilities
and
built-in shell commands are at your disposal. This article
examines
several that you must know if you want to move beyond
beginning
scripting. They are, in order of discussion:
read built-in
case built-in
break built-in
exit built-in
tr external
expr external
typeset built-in, ksh only
Using these commands within scripts removes the static
limitations of
always performing the same, mundane tasks that must
exist in their
absence.
Accepting User Input
There are essentially three ways to introduce variables
to scripts. The
first is to place them on the command line and look
for them using
variations of $1-$9. The second is to read them from
a static file into
which you place the variables that remain consistent,
and the third is
to enter them into the script, as it is running, from
the keyboard.
Whenever a read command is encountered in a script,
processing stops and
input is read into a named variable(s) until the newline
character is
entered. White space (be it a space or a tab) is used
as the delimiter
between variables, and excess words are stored in the
last variable. The
syntax is simply:
read variable
For example, if the script contains this set of lines:
clear
read name
echo $name
the screen will clear, processing will stop and wait
until a value is
read in and the newline character entered, then it will
echo the value
of what was read in, and go on. An echo command is used
to prompt the
user, as in:
clear
echo "Enter your name:c" read name
echo $name
More than one read command can be used to bring in additional
variables,
or they can be simultaneously obtained by the same read
command. For
example:
clear
echo "Enter your name:c" read fname lname
echo "Pleased to meet you $fname. Are you related to
$lname?"
In this scenario, if the keyboard operator enters "Kristin
Evan," then
the value of the variable fname becomes "Kristin,"
and the value of the
lname variable becomes "Evan." The white space
placed between the two at
time of entry serves as the delimiter signifying the
end of fname and
the beginning of lname. It is extremely important to
note what was
mentioned before; the last variable read takes on all
remaining
characters.
Therefore, if the keyboard operator enters at the prompt
"Kristin Karen
Evan," then fname remains "Kristin,"
but lname becomes "Karen Evan." In
all cases, using only one variable on the line for read
has the effect
of assigning the entire line to the variable. Compare
input and act,
accordingly, with case.
Multiple Choices
The case command is intended for use when there are
a number of possible
operations that can take place based upon the value
of a variable. A
perfect example would be a menu - choices are displayed,
the user picks
one, and the operations carried out are those limited
to that choice.
The syntax for the case command is shown below:
case {value} in
pat1)
command
command
;;
pat2)
command
command
;;
esac
Notice the esac at the end of the options list. This
signifies the end
of the case list in the same manner fi signifies the
end of if
statements. The purpose of the double semicolons (;;)
is to end the case
command at that point. Thus, if pat1 is entered, the
lines specified for
it are executed, and the case command exited. Without
the double
semicolons, the processing would continue until a pair
were met, or the
end of the case list. This provides a means by which
multiple-line
commands can be entered for each choice. If no patterns
match the value,
then no commands are executed.
The following is a simple example:
echo "\n\nEnter a letter from A - E:"
read lett
case $lett in
A) echo "ASCII value is 65";;
B) echo "ASCII value is 66";;
C) echo "ASCII value is 67";;
D) echo "ASCII value is 68";;
E) echo "ASCII value is 69";;
esac
If the keyboard operator types in "A," the
response they get is that the
equivalent ascii value is 65. If they type in "C,"
they learn that it is
67. If they type in "F," then it does not
match any of the choices, and
nothing is echoed back to them.
It is important to understand that case will execute
the first line
matching the selection, and no others. Thus, if you
had something like:
echo "\n\nEnter a letter from A - E:"
read lett
case $lett in
A) echo "ASCII value is 65";;
B) echo "ASCII value is 66";;
C) echo "ASCII value is 67";;
D) echo "ASCII value is 68";;
E) echo "ASCII value is 69";;
A) echo "You picked this
again?;;
esac
the last "A" choice would never execute. Once
a match is found for the
variable, and the double semicolons are encountered,
the routine drops
to esac and leaves. Therefore, you must place a bottom
choice in the
list that matches anything that made it this far without
triggering
execution of a command. This provides a means of dealing
with
exceptions, as in:
echo "\n\nEnter a letter from A - E:"
read lett
case $lett in
A) echo "ASCII value is 65";;
B) echo "ASCII value is 66";;
C) echo "ASCII value is 67";;
D) echo "ASCII value is 68";;
E) echo "ASCII value is 69";;
*) echo "You are beyond the
realm of
choices\07\07";;
esac
In this routine, anything not matching "A"
through "E" causes an error
message and two beeps of the terminal.
Patterns can utilize metacharacters, *, ?, {...}, and
the pipe (|)
symbol to signify one or another pattern. For example:
echo "\n\nEnter a letter from A - E:"
read lett
case $lett in
A) echo "ASCII value is 65";;
B) echo "ASCII value is 66";;
C) echo "ASCII value is 67";;
D) echo "ASCII value is 68";;
E) echo "ASCII value is 69";;
Q|q) exit ;;
[1-9])echo "Only letters may be entered";;
*) echo "You are beyond the realm of
choices\07\07";;
esac
Now, if any number is entered (as opposed to a letter),
a standard
message is returned. The routine can be exited using
either "Q" or "q."
The exit command, within a case routine, exits the entire
shell script.
Therefore, if there are lines meant to run after the
case routine, do
not use exit, because they never will. An alternative
to exit is break.
When you use break as a choice, the execution leaves
the entire case
routine (drops beneath esac), and the rest of the script
is carried out.
Using tr
The tr utility translates one character set to another
as it copies
standard input to standard output. The syntax is:
tr {first set} {second set}
For example:
tr "[a-z]" "[A-Z]"
will convert all lowercase characters from a to z into
their
corresponding uppercase character. In all cases, the
to-string must be
the same number of characters as the from-string, otherwise
unexpected
(or unwanted) results will occur. The tr command can
also be used to
change the value of an existing variable, using the
following syntax:
x=`echo $1 | tr "[a-z]" "[A-Z]"`
There are a handful of parameters that can be used with
the utility:
-c |
Restricts output to the characters specified in from-string and appends characters
contained in to-string. |
-d |
Deletes characters specified in from-string. |
-s |
Strips repeated characters generated in the output by those specified in to-string. |
[c1-cn] |
Specifies a range of ASCII characters, from c1 to cn. |
[c*n] |
Specifies that character c repeats n times. The n may be zero or left blank, which assumes a huge number of
the character c. |
expr
Under normal operation, variables are always treated
as string text and
cannot be added together, increased, decreased, or otherwise
arithmetically manipulated within the shell. Following
is an example of
the result of treating string variables numerically:
$ i=7
$ i=i+5
$ echo i
i
$ echo $i
i+5
$
One of the primary purposes of the expr utility is to
interpret
variables from strings. Numbers are separated by blanks,
and strings
should be quoted. Minus signs can be used to denote
negative numbers,
and all integers are treated as 32-bit, 2's complement
numbers.
There are a great many possibilities present with expr,
but the most
useful are those pertaining to arithmetic. Operators
that can be used in
operations are shown in Figure 1. If expr has a problem
during
operation, it will return an exit value to the shell
of:
0 if the expression is neither null nor 0.
1 if the expression is null or 0.
2 for invalid expressions.
Figure 2 shows five examples of operation.
A simple, incrementing script can be written in a file
in the following
fashion:
while true
do
i=`expr $i + 1`
echo $i
done
Using while true in this manner creates an endless loop
because the test
can never be untrue. expr will continue to increment
the value of i and
display it on the screen until you break out of it.
To limit the number
of times this routine runs, without breaking out of
it, change the test
condition to something that exists for only a short
period, such as:
while [`date '+%M'` + 02]
which will only run for one minute (naturally replace
"02" with the
current minute).
Using typeset Instead of expr
The typeset utility exists in the Korn shell only. If
you are using
Bourne, you must stick with expr to mathematically manipulate
variables.
The Korn shell, via the typeset utility, allows you
to define, or set, a
variable as possessing definition or a type, which makes
it stand out.
You can define a variable as always being one of the
following:
uppercase -u
lowercase -l
left justified -L
right justified -R
read only -r
exported -x
an integer -i
It is the integer type (-i) of most interest for using
typeset in place
of expr. A subcommand, if you will, of typeset is let.
let is used to
change values, as in the following example:
typeset -i x y
x=$1
y=1
until [ "$y" < "$x" ]
do
print $y
let y="$y+1"
done
This routine establishes two variables as being integers,
x and y. Next,
the value of x is defined as a variable passed on the
command line, and
y is set to 1. Until y is one greater than x, the routine
will print
numbers from 1 to x. Therefore, if 7 is entered at the
command line, the
numbers 1 through 7 are printed. If 600 is entered on
the command, the
numbers 1 through 600 are printed.
An Example
Listing 1 shows an example of several of the utilities
discussed here
(the line numbers were added to aid explanation only).
This is a
standard menu utility, simply called MENU. Lines 2 through
14 clear the
screen and print the menu choices. Line 14 also mentions
that the user
can press "Q" to quit the utility.
Line 15 accepts the input while line 16 translates any
lowercase
characters into uppercase - this will come in useful
later when offering
the choice to quit or obtain help. Line 17 echoes a
portion of the date
and the number of the menu choice made to a logfile
that can be used to
keep track of transactions.
Line 18 begins the case routine, which runs through
line 73. Lines 19
through 21 comprise the first choice, and lines 22 through
37 are the
second choice. Within the second choice, notice the
additional prompt
for input and lines 24 and 26.
Lines 38 through 47 are the third choice - with another
read statement
embedded within. The fourth selection begins on line
48 and runs through
line 58. Notice the additional case routine embedded
within it that
begins on line 52.
Line 60 begins the "Q" choice allowing the
user to quit. Notice the two
exit commands within the choice. The first exits from
the routine
itself. The second kills the shell and knocks the user
back to a login.
Thus, this routine is limited to only being run by a
special login
defined in a user profile. When not running this routine,
the user must
log in by another identity.
Line 65 is where a help facility could be installed,
and line 69 catches
all invalid choices. Line 73 ends the case statement,
and line 74 calls
the routine all over again, restarting the menu on the
screen.
If this script (shown in Listing 1) were running in
a Korn shell
environment, the Help and Quit functions could be assigned
to menu
choices 5 and 6, respectively. The variable read in
could then be
typeset as an integer and a test done to see whether
the value was more
than six or less than one. If so, an error message indicating
the
invalid choice would be delivered prior to getting to
the case routine,
and the user would be returned to the menu.
Conclusion
The utilities discussed can add power and performance
to your shell
scripts in a manner that cannot be equaled without them.
Familiarizing
yourself with them and the options they offer will allow
you to write
very good script routines.
About the Author
Emmett Dulaney is a Publishing Manager for New Riders
Publishing and the
author of several books on Unix and TCP/IP. He can be
reached on
Compuserve at 74507,3713.
|