next up previous contents
Next: Loop Control Up: Bourne Shell Programming Previous: String Comparisons

Testing Files

A very important test is often the status of a file. Perhaps one test would be to test if a file exists before creating another with the same name (which would of course destroy and replace the pre-existing file). The test command will test files for such attributes as existence, writability, readability and file type. Table 2.6 summarizes the file test formats and return values.

  table135
Table 2.6:  File test formats and return codes.

To illustrate the use of file testing, the following is a script that acts as a revised version of the Unix move (mv) command. Not only does this script check for overwrites, it also deletes any file that is of zero size.

#! /bin/sh
#
# Move is a script that moves a file if no overwrite will occur
# and will delete the file to be moved if it is empty
#
# Usage: move [file] [destination]
# 
if  [ ! -s $1 ]
then
   if rm $1 2>/dev/null
   then 
       echo "$1 was empty and thus removed"
   else
       echo "$1 does not exist"
   fi
else
   mv -i $1 $2
fi

This example uses the if:then:else construct but also uses a few things not discussed in much detail. The first new idea was briefly covered in the section on variables - parameters. The $1 passes the name of the file being moved (the first name entered on the command-line after move), and the $2 is the destination or place that the file is moving to. The Bourne shell allows nine such parameters, each separated by white space, and therefore nine pieces of information can be passed to a shell program from the command-line.

While the Bourne shell only accommodates direct access to nine parameters (i.e. $1 through $9), nothing stops the user from entering more. The shell comes with a tool that permits access to more than nine parameters. More generally, this tool permits the use of an undetermined, at run time, number of parameters. This means that the script can let the user enter as many parameters as he wishes on the command line and the program will handle any or all of them as required. This tool is the shift command. The shift command slides the parameter list by a specified number of places to the left and has the following form:

shift n

where n is the an integer that specifies how many places the list will be adjusted. If n is not explicitly given, the parameter list is moved one place. The following illustrates how the shift command works on a parameter list of size n. Before the shift command has been used, the list would look like

$1 $2 $3 $4 ... $n

and after using shift:

$2 $3 $4 $5 ... $(n-1)

where after the shift command was used, the first parameter is no longer accessible. The number of parameters in the parameter list drops by the number of places that the list was shifted as well. For example, if a user inputed 5 parameters and then the script shifted the list:

echo $#
5
shift
echo $#
4

where the 4 and 5 would be the output of the shell script. This presents a clever way of accessing any number of parameters in a script. To ensure that all parameters have been processed, a simple test command can be incorporated into the script as follows:

while [ $# -ge 1 ]
   do 
   echo $1
    .
    .
    .
   shift
   done

This script piece will print the parameters, carry out some unspecified tasks, and repeat until all of the parameters passed have been exhausted.

If a script is written to expect a certain number of parameters, and the user places too many on the command line, the program will only use as many as were required. This means that if the script expects 2 parameters, and the user enters 3, only the first two entered will be used. If, on the other hand, the user enters fewer parameters than the script was written to expect, it will fill the un-entered parameters with a null value (i.e. they will be empty).

Another new idea (from the previous example script) was that of having one if statement within another, which is called a nested if statement. This allows tests and decisions to be made as a result of earlier tests. Nested decisions allow very specific testing with less coding than would otherwise be the case. Notice that the first if statement uses a ! character, which negates the test, or turns a TRUE value to a FALSE value and vice versa.

The comments have been included to give some description of the task the script will carry out. One commented line in particular gives the expected usage of the script which allows the user to have knowledge of which order to enter the parameters on the command-line. This is not required but is good practice - especially if the script is to be used by people other than the programmer.

There is a third type of test that can be made. This is the integer comparison test. This type of test is done when comparing the values of two integers. The first integer, the one on the left, is compared with the one on the right. For instance, the following tests to see whether Integer1 is greater than Integer2:

$ test Integer1 -gt Integer2

There are in total six comparisons that can be made between two integers: if they are equal (-eq); if the are not equal (-ne); if Integer1 is greater than Integer2 (-gt); if Integer1 is greater than or equal to Integer2 (-ge); if Integer1 is less than Integer2 (-lt); and finally, if Integer1 is less than or equal to Integer2 (-le).

In the Bourne shell, mathematical expressions cannot be simply assigned to a variable. This means that the expression a=b+c is not valid. To get the result of an arithmetic expression, the expr command must be used. This command evaluates the arithmetic expression, and then returns the result. There are five arithmetic operators that can be used in the Bourne shell:

  table145
Table 2.7: Arithmetic operators in the Bourne shell.

The multiplication and division operators have higher presidence than do the addition and subtraction operators. The order can be arranged in any fashion however, with the use of back quotations (`). The Bourne shell does not recognize parenthesese. For example,

$ expr 3 + 2 \* 8
19

is different from

$ expr `3 + 2` \* 8
40

Note also, that the multiplication operator (*) was escaped in the expression. This is because the shell tries to use the charater as a filename expansion if it is not escaped. One last important note on arithmetic expressions is that the division operator is integer division. This means that when one integer is divided by another integer and the result is not itself an integer, the remainder is dropped and only the integer portion of the division is returned. The modulus operator can be used however to return the remainder of the operation.

It may be neccessary to use more sophisticated compound tests in a decision, and therefore tests can be combined using logical AND and OR operators. A compound test is a fancy way of saying that two simple tests are combined into a single test using logical operators. The logical AND operator is written -a, while the logical OR operator is written -o. To illustrate the use of a compound test, consider the situation where a file is tested for readability as well as for writability:

if [ -r prog_name -a -w prog_name ]
then
   command list
fi

A test can also be negated by placing the ! character before the test:

if [ ! -x prog_name ]
then
   lpr prog_name
else
   echo ``Are you sure you want to print an executable file?''
fi

The if:then:else construct works well for handling one or more decisions in the flow of a script, but an even better construct is available for handling decisions where many choices are available - the case construct. In these situations, the case construct is simplier to impliment than the if:then:else construct. The case statement takes a value, or pattern, and compares it with a list of patterns. The number of patterns is up to the programmer's discression, but there is no imposed limit. Along with each pattern in the list is a list of commands. The first pattern that matches the initial value has its command list executed. The case construct has the following format

case value in
   pattern1)
       command list;;
   pattern2)
       command list;;
       .
       .
       .
   patternN)
       command list
esac

The double semicolons are used to signify the end of a command list. The last group of commands does not require the double semicolons as the esac statement signifies the end of the construct, which implies the end of the last group of commands. More than one pattern can be entered on a line if they are separated by the logical OR symbol (|). When multiple patterns are used in this way, the first line that has any one of its patterns matched has its command list executed. Character substitutions, such as * and [], can also be used in a pattern. Since the patterns in the comparison list are compared from top to bottom it is always good practice to use the meta-character (*) as the last pattern choice to handle any unexpected choices. The case construct is especially useful in menus of choices such as the following:

echo ``Do you wish to delete $FILE ?''
echo ' '
echo ``q or quit  Quits''
read ANSWER
case $ANSWER in
   y|Y|yes|YES)
      echo ``Removing $FILE'';
      rm $FILE;;
   n|N|no|NO)
      echo ``$FILE was not removed'';;
   q|Q|quit|QUIT)
      exit 1;;
   *)
      echo ``$ANSWER was not an understood option'';
      exit 2
esac

The only command used here which is yet to be covered is the read command which gets input from the user. This is covered in the section on input-output.


next up previous contents
Next: Loop Control Up: Bourne Shell Programming Previous: String Comparisons

Douglas M Gingrich
Mon Apr 27 15:25:49 MDT 1998