In this chapter, we look at shell's simplest way of selecting between alternative actions and at its simplest way of repeating commands.
In chapter ??, we were embarrassed by
bu
not being able to behave well if not given exactly
one parameter.
We need to make
bu
select between two courses of actions.
Using shell's
case
statement will allow us to do that but first let's see the
general shape of
case.
The
man
pages for
sh
say the format is like this
case word in [pattern[ | pattern] ... ) list ;; ] ... esac
However, when used in real scripts it is usually spread over several lines and indented - like this:
case word in
pattern )
list
;;
pattern )
list
;;
pattern )
list
;;
esac
Here, the optional
| pattern
has been omitted and the optional
pattern ) list ;;
has been used three times.
The
word
must be replaced with the value of a variable or parameter.
The
list
must be replaced with a sequence of Unix commands.
The only fixed part of the layout is that the
case,
esac
and the
patterns
must be the first thing on their lines.
If
esac
is puzzling you, it is
case
backwards!
The way
case
works is that shell takes the
word
and compares it with the
patterns.
If a match is found, the commands in the
list
after the pattern are executed as far as the
;;.
Shell then skips to the line after
esac.
Here then is an improved
bu
using the
case
command:
$ more bu
case $# in
0) echo usage: bu file
;;
1) file=$1
cp $file $file.bu
echo $file backed-up in $file.bu
;;
2) echo usage: bu file
;;
esac
$
In this version of
bu,
$#, the number of parameters,
has been used as the
word
in the
case
statement.
Each of the three patterns is simply a single digit.
The first
list
has been replaced by one Unix command,
the second
list
has been replaced by three Unix commands, and the last
list
has been replaced by one Unix command.
Shell checks the word against the patterns to select which
list
of commands to execute.
This version will not work with three or more parameters because
the case statement does not include the patterns: 3, 4, and so on.
To correct it,
we need to use pattern matching like shell uses with file names.
The main facilities are
*
and
?.
*
matches any string of characters and
?
matches any single character.
You need to read the manual carefully for all the options.
Using
*
allows us improve
bu:
$ more bu
case $# in
1) file=$1
cp $file $file.bu
echo $file backed-up in $file.bu
;;
*) echo usage: bu file
;;
esac
$
Be careful to put the
*
pattern last, otherwise shell will never find the earlier patterns
because
*
matches any string including
1!
Our
bu
command is still not as useful as it could be.
Why can't we back-up more than one file at once?
To do that we need to know shell's
for
statement.
According to the
man
pages for
sh, it has this format:
for name [ in word ... ] do list done
This example shows the usual layout:
for green in Carson Kelly Lambert
do echo Ms $green is a well-known green.
echo
done
As you can see,
name
has been replaced with the variable
green;
word ...
has been replaced with a list of actual words; and
list
has been replaced with two
echo
commands.
When shell executes a
for
it puts the first word in the list of words into the
named variable and performs the Unix commands between
do
and
done.
Then it repeats the commands with the second and subsequent words
from the list.
Shell insists that
for,
do
and
done
are the first words on their lines.
Here is the output from the previous example:
$ greens
Ms Carson is a well-known green.
Ms Kelly is a well-known green.
Ms Lambert is a well-known green.
$
The above form of
for
won't quite do for
bu.
If you look carefully at the format of
for, you will see that
in word ...
is optional.
If we don't supply any words, shell will use the parameters one by
one.
We can use that to step through as many files as we like.
Making the changes, we get:
$ more bu
case $# in
0) echo usage: bu file ...
;;
*) for file
do cp $file $file.bu
echo $file backed-up in $file.bu
done
;;
esac
Now, the only thing
bu
has to check for is zero parameters;
any other number causes the
for
statement to be executed.
A very important idea has sneaked in here so let's spell it out:
There is no problem in using a
for
inside a
case
or vice versa.
This applies to all of shell's selection and repetition facilities.
Of course, proper indentation helps the reader
follow what is happening.
If we create another valuable file, we can use the new, improved
bu
on it.
$ date > priceless $ bu precious priceless precious backed-up in precious.bu priceless backed-up in priceless.bu $ ls pr* precious precious.bu priceless priceless.bu $
Our
bu
script goes from strength to strength!
Another point to notice about the previous example is that it will deal with any number of parameters. Because we don't try to refer to them by number, there is no problem handling parameters ten, eleven and so on, or even parameters 100, 101 and upwards!
The
for
and
case
statements are very powerful and simple;
they rely on lists of
words and on pattern matching, instead of relying on condition tests.
They allow us to do things we would normally expect to use the (more
complicated)
if
or
while
statements for.
The following example demonstrates their power:
$ more picky
for file
do case $file in
*.txt)
echo "can't do .txt files"
exit
;;
fred.*)
echo "can't do files called fred.*"
exit
;;
*'*'*)
echo "can't handle asterisks in file names"
exit
;;
esac
done
echo all OK
$
The
exit
statement is new to us; it causes the shell to stop running the script,
without executing any more statements.
The
picky
script displays an error message and stops
if it finds a filename it can't handle in its parameters.
For example:
$ picky tom.gif dick.jpeg harry.html all OK $ picky tom.gif dick.jpeg fred.any harry.html can't do files called fred.* $
This is very straightforward code to do such complex checking.
For each of the following, you have to write a shell script. Name them `q12.1' ...
Write a shell script to append one file to another. The
parameters are both file names. If only one name is given, the
script should append from the standard input to the named file.
If two names are given, the first named file should be appended to
the second. Hint:
cat
copies its input to its output.
Answer
$ more q12.1 case $# in 1) cat >> $1 ;; 2) cat $1 >> $2 ;; esac $ > file1 $ > file2 $ q12.1 file1 a line of text for file1 and another ^D $ q12.1 file2 a line for file2 ^D $ q12.1 file1 file2 $ more file2 a line for file2 a line of text for file1 and another $
Extend the script from question one so that it displays a usage message if given anything other than one or two parameters.
Answer
$ more q12.2 case $# in 1) cat >> $1 ;; 2) cat $1 >> $2 ;; *) echo 'Usage: q12.2 [from] to' esac $ q12.2 Usage: q12.2 [from] to $
Write a script to do an
ls
of the directories `Tom', `Jerry' and `Bonzo' with appropriate headings.
Answer
$ more q12.3 for directory in Tom Jerry Bonzo do cd $directory echo This is an ls of $directory: ls cd .. done $ mkdir Tom Jerry Bonzo $ q12.3 This is an ls of Tom: This is an ls of Jerry: This is an ls of Bonzo: $
Construct a shell script to display its parameters one per line.
Answer
$ more q12.4 for parameter do echo $parameter done $ q12.4 f1 f2 f3 f1 f2 f3 $
In each of the answers the script is displayed using
more; then it is executed by typing its name.
$ more q12.1 case $# in 1) cat >> $1 ;; 2) cat $1 >> $2 ;; esac $ > file1 $ > file2 $ q12.1 file1 a line of text for file1 and another ^D $ q12.1 file2 a line for file2 ^D $ q12.1 file1 file2 $ more file2 a line for file2 a line of text for file1 and another $
$ more q12.2 case $# in 1) cat >> $1 ;; 2) cat $1 >> $2 ;; *) echo 'Usage: q12.2 [from] to' esac $ q12.2 Usage: q12.2 [from] to $
$ more q12.3 for directory in Tom Jerry Bonzo do cd $directory echo This is an ls of $directory: ls cd .. done $ mkdir Tom Jerry Bonzo $ q12.3 This is an ls of Tom: This is an ls of Jerry: This is an ls of Bonzo: $
$ more q12.4 for parameter do echo $parameter done $ q12.4 f1 f2 f3 f1 f2 f3 $
http://homepages.shu.ac.uk/~cmsps/unix/casefor.html
Last updated: Thursday 05 April 2012 at 17:45