Previous Section  < Day Day Up >  Next Section

5.3. case

The next flow-control construct we will cover is case. While the case statement in Pascal and the similar switch statement in Java and C can be used to test simple values like integers and characters, bash's case construct lets you test strings against patterns that can contain wildcard characters. Like its conventional-language counterparts, case lets you express a series of if-then-else type statements in a concise way.

The syntax of case is as follows:

case expression  in 

    pattern1  )

        statements ;; 

 pattern2  )

        statements ;; 

 ... 

esac 

Any of the patterns can actually be several patterns separated by pipe characters (|). If expression matches one of the patterns, its corresponding statements are executed. If there are several patterns separated by pipe characters, the expression can match any of them in order for the associated statements to be run. The patterns are checked in order until a match is found; if none is found, nothing happens.

This construct should become clearer with an example. Let's revisit our solution to Task 4-2 and the additions to it presented earlier in this chapter (our graphics utility). Remember that we wrote some code that processed input files according to their suffixes ( .pcx for PCX format, .gif for GIF format, etc.).

We can improve upon this solution in two ways. Firstly, we can use a for loop to allow multiple files to be processed one at a time; secondly, we can use the case construct to streamline the code:

for filename in "$@"; do

    pnmfile=${filename%.*}.ppm



    case $filename in

        *.jpg ) exit 0 ;;



        *.tga ) tgatoppm $filename > $pnmfile ;;



        *.xpm ) xpmtoppm $filename > $pnmfile ;;



        *.pcx ) pcxtoppm $filename > $pnmfile ;;



        *.tif ) tifftopnm $filename > $pnmfile ;;



        *.gif ) giftopnm $filename > $pnmfile ;;



            * ) echo "procfile: $filename is an unknown graphics file."

                exit 1 ;;

    esac



    outfile=${pnmfile%.ppm}.new.jpg



    pnmtojpeg $pnmfile > $outfile



    rm $pnmfile



done

The case construct in this code does the same thing as the if statements that we saw in the earlier version. It is, however, clearer and easier to follow.

The first six patterns in the case statement match the various file extensions that we wish to process. The last pattern matches anything that hasn't already been matched by the previous statements. It is essentially a catchall and is analogous to the default case in C.

There is another slight difference to the previous version; we have moved the pattern matching and replacement inside the added for loop that processes all of the command-line arguments. Each time we pass through the loop, we want to create a temporary and final file with a name based on the name in the current command-line argument.

We'll return to this example in Chapter 6, when we further develop the script and discuss how to handle dash options on the command line. In the meantime, here is a task that requires that we use case.

Task 5-4

Write a function that implements the Korn shell's cd old new. cd takes the pathname of the current directory and tries to find the string old. If it finds it, it substitutes new and attempts to change to the resulting directory.


We can implement this by using a case statement to check the number of arguments and the built-in cd command to do the actual change of directory.

Here is the code:[10]

[10] To make the function a little clearer, we've used some advanced I/O redirection. I/O redirection is covered in Chapter 7.

cd( )

{

    case "$#" in

        0 | 1)  builtin cd $1 ;;

        2    ) newdir=${PWD//$1/$2}

                case "$newdir" in

                    $PWD)   echo "bash: cd: bad substitution" >&2 ;

                        return 1 ;;

                    *   )   builtin cd "$newdir" ;;

                esac ;;

        *    )  echo "bash: cd: wrong arg count" 1>&2 ; return 1 ;;

    esac

}

The case statement in this task tests the number of arguments to our cd command against three alternatives.

For zero or one arguments, we want our cd to work just like the built-in one. The first alternative in the case statement does this. It includes something we haven't used so far; the pipe symbol between the 0 and 1 means that either pattern is an acceptable match. If the number of arguments is either of these, the built-in cd is executed.

The next alternative is for two arguments, which is where we'll add the new functionality to cd. The first thing that has to be done is finding and replacing the old string with the new one. We use the pattern matching and replacement that we saw in the last chapter, the result being assigned to newdir. If the substitution didn't take place, the pathname will be unchanged. We'll use this fact in the next few lines.

Another case statement chooses between performing the cd or reporting an error because the new directory is unchanged. The * alternative is a catchall for anything other than the current pathname (caught by the first alternative).

You might notice one small problem with this code: if your old and new strings are the same you'll get bash:: cd: bad substitution. It should just leave you in the same directory with no error message, but because the directory path doesn't change, it uses the first alternative in the inner case statement. The problem lies in knowing if sed has performed a substitution or not. You might like to think about ways to fix this problem (hint: you could use grep to check whether the pathname has the old string in it).

The last alternative in the outer case statement prints an error message if there are more than two arguments.

    Previous Section  < Day Day Up >  Next Section