Sams Teach Yourself Shell Programming in 24 Hours
(Publisher: Macmillan Computer Publishing)
Author(s): Sriranga Veeraraghavan
ISBN: 0672314819
Publication Date: 01/01/99

Previous Table of Contents Next


Common Argument Handling Problems

Now that the mytar script uses options to set the mode in which the script runs, you have another problem to solve. Namely, what should it do if the second argument, $2, is not provided?

You don’t have to worry about what happens if the first argument, $1, is not provided because the case statement deals with this situation via the default case, *.

The simplest method for checking the necessary number of arguments is to see whether the number of given arguments, $#, matches the number of required arguments. Add this check to the script:

#!/bin/sh

USAGE="Usage: 'basename $0' [-c|-t] [file|directory]"

if [ $# -lt 2 ] ; then
    echo "$USAGE"
    exit 1

fi

case "$1" in
    -t) TARGS="-tvf $2" ;;
    -c) TARGS="-cvf $2.tar $2" ;;
     *) echo "$USAGE"
        exit 0
        ;;

esac

tar $TARGS

Handling Additional Files

This mytar script is mostly finished, but you can still make a few improvements. For example, it only deals with the first file that is given as an argument, and it does not check to see whether the file argument is really a file.

You can add the processing of all file arguments by using the special shell variable $@. Start with the –t (list contents) option. The case statement now becomes

case "$1" in
    -t) TARGS="-tvf"
        for i in "$@" ; do
            if [ -f "$i" ] ; then tar $TARGS "$i" ; fi ;
        done
        ;;
    -c) TARGS="-cvf $2.tar $2" ;
        tar $TARGS
        ;;
     *) echo "$USAGE" ;
        exit 0
        ;;
esac

The main change is that the –t case now includes a for loop that cycles through the arguments and checks to see whether each one is a file. If an argument is a file, tar is invoked on that file.


Caution:  
When examining the arguments passed to a script, two special variables are available for inspection, $* and $@.

The main difference between these two is how they expand arguments. When $* is used, it simply expands each argument without preserving quoting. This can sometimes cause a problem. If your script is given a filename containing spaces as an argument,

mytar –t "my tar file.tar"

using $* would mean that the for loop would call tar three times for files named my, tar, and file.tar, instead of once for the file you requested: my tar file.tar.

By using $@, you avoid this problem because it expands each argument as it was quoted on the command line.


Some Minor Issues

You should deal with a few more minor issues. Looking closely, you see that all the arguments given to the script, including the first argument, $1, are considered as files. Because you are using the first argument as the flag to indicate the mode in which the script runs, you should not consider it.

Not only does this reduce the number of times the for loop runs, but it also prevents the script from accidentally trying to run tar on a file with the name -t. To remove the first argument from the list of arguments, use the shift command. A similar change to the make mode of the script is also required.

Another issue is what the script should do when an operation fails. In the case of the listing operation, if the tar cannot list the contents of a file, skipping the file and printing an error would be a reasonable operation. Because the shell sets the variable $? to the exit status of the most recent command, you can use that to determine whether a tar operation failed.

Resolving the previous issues, your script is as follows:

#!/bin/sh

USAGE="Usage: 'basename $0' [-c|-t] [files|directories]"

if [ $# -lt 2 ] ; then
    echo "$USAGE" ;
    exit 1 ;
fi

case "$1" in
    -t) shift ; TARGS="-tvf" ;
        for i in "$@" ; do
            if [ -f "$i" ] ; then
                FILES='tar $TARGS "$i" 2>/dev/null'
                if [ $? -eq 0 ] ; then
                    echo ; echo "$i" ; echo "$FILES"
                else
                    echo "ERROR: $i not a tar file."
                fi
            else
                echo "ERROR: $i not a file."
            fi
        done
        ;;
    -c) shift ; TARGS="-cvf" ;
        tar $TARGS archive.tar "$@"
        ;;
     *) echo "$USAGE"
        exit 0
        ;;
esac
exit $?

Option Parsing in Shell Scripts

You have two common ways to handle the parsing of options passed to a shell script. In the first method, you can manually deal with the options using a case statement. This method was used in the mytar script presented earlier in the chapter. The second method, discussed in this section, is to use the getopts command.

The syntax of the getopts command is

getopts option-string variable

Here option-string is a string consisting of all the single character options getopts should consider, and variable is the name of the variable that the option should be set to. Usually the variable used is named OPTION.

The process by which getopts parses the options given on the command line is

1.  The getopts option examines all the command line arguments, looking for arguments starting with the character.
2.  When an argument starting with the character is found, it compares the characters following the to the characters given in the option-string.
3.  If a match is found, the specified variable is set to the option: otherwise, variable is set to the ? character.
4.  Steps 1 through 3 are repeated until all the options have been considered.
5.  When parsing has finished, getopts returns a nonzero exit code. This allows it to be easily used in loops. Also, when getopts has finished, it sets the variable OPTIND to the index of the last argument.

Another feature of getopts is the capability to indicate options requiring an additional parameter. You can accomplish this by following the option with a : character in the option-string. In this case, after an option is parsed, the additional parameter is set to the value of the variable named OPTARG.


Previous Table of Contents Next