Further Use of Unix: 7 Bourne Shell Programming Structures


7.1 Overview

The C shell (and tcsh) offers a shell programming language. However, as all Unix systems have the original Bourne shell available, all the script examples in this section have been written using the Bourne shell to interpret. The Bourne shell programming language is also commonly thought to be more straightforward for many tasks. All the examples in this section have been rewritten using the C shell programming and included in Appendix 3.

Each shell language has its own features, advantages and disadvantages and ease of use. The shell you decide to use for scripts will depend on personal preference and what is available to you.

Help: For more information see sh(1). For a useful Internet source of help on Bourne Shell programming see the section on WWW sites.

7.2 Basic Shell Script

To create a file, the output of cat can be redirected. Type the commands, then end with <Ctrl/d>, or any editor can be used to create the script. The editor used on this course is the ubiquitous emacs editor.

All scripts should begin with a # on the first line otherwise a Bourne shell will be invoked. It is possible to state explicitly which executable (i.e. which shell) should execute a script by placing ! program name directly after the #, for example #! /bin/sh would load a Bourne shell, #! /bin/csh would load a csh.

After the first line any other lines that begin with # are ignored and are therefore used as a way of adding comments to shell scripts. Here is a simple example of a Bourne shell script for the earlier howmany example.

#! /bin/sh
# Bourne Shell Script - to display number of current logins
echo -n "Users logged in :" ; who | wc -l

7.3 Creating Shell Variables

When you execute a shell script, a new shell comes into existence for its duration, disappearing when execution terminates. It is therefore not possible to make lasting changes to shell variables using an ordinary shell script. It is not necessary to remove shell variables used in shell scripts as they will be local to the shell that runs the script.

Here is an example Bourne shell script containing variables with the resultant output in bold.

#!/bin/sh
a=hello
b="hello world"
echo $a
echo $b

hello
hello world

a is a standard shell variable, b is a string variable. It is possible to access the value of a variable by preceding it with $.

When assigning values to variables there must be no spaces around the equals sign (=).

7.3.1 Performing Arithmetical Operations on Shell Variables

For Bourne Shell programming, use of expr can be made for arithmetical operations. expr evaluates an expression and writes the result to the standard output stream. To actually assign the value of an arithmetic instruction to a variable, back quotes are required:

i=7
j=13
k=`expr $i + $j`
echo $k

20

Without use of expr, for example, k=a+b sets k equal to the string a+b and k=$a+$b, sets k to the string 7+13. The spacing is important: each term being evaluated by expr must have a space surrounding it and the assignment to k must have no spaces around the equals sign.

Note also that without the back quotes, ie. k= expr $a + $b would simply result in 20 being written to standard output but k would not hold the new value. The back-quotes (`) mean "do this command, substitute the result here".

Help: For more information see expr(1).

7.4 Positional Parameters and Variables in Scripts

Any arguments that are specified in a command line are accessible within a shell script. You can visualise the command as a subroutine with parameters. Suppose we had a shell script called Rename, used to rename a file. The script would need to take two arguments: the original filename and the new filename.

Rename poem six-verses

The shell script might look something like this:

#! /bin/sh
# Rename sh script
# Lines starting with # are comments
mv $1 $2
echo "$1 renamed as $2. The Unix Rename command is mv"

$1 and $2 are the first and second positional parameters of this shell script. In this example $1 will store the string poem and $2 will be six-verses.

Argument, Meaning

$0
name of script
$1
first argument to script
$2
second argument to script (and $3 for 3rd etc.)
$*
all arguments to script
$$
current process number of shell

7.5 Command Results as Arguments

As well as being able to redirect the input and output of commands, the shell can also execute a command and make its result available as an argument to another command. For example, suppose we want to find out on which port tina is logged in. We could use

who | grep tina
tina ttyp3 Sep 17 13:58 (OXVAXC)

To isolate the port, we could bring in awk to extract the second field of the output:

who | grep tina | awk ' { print $2 } '
ttyp3

This result could be assigned to a variable, WHICHPORT, as follows:

WHICHPORT=`who | grep tina | awk ' { print $2 } '`

The back-quotes (`) mean "do this command, substitute the result here".

All of this could be combined in a shell script finduser which takes a username as its argument:

#! /bin/sh
# sh script to rename files
WHICHPORT=`who | grep $1 | awk ' {print $2}'`
echo $1 is logged in from $WHICHPORT

Note that the $1 refers to the first positional argument given to the script and $2 means the second field of output given to awk.

7.6 Debugging Scripts

When a shell script is invoked, a shell is started and the script is run in that shell. This happens automatically. The same effect can be achieved by typing the name of the desired shell and then the command name (and any arguments required):

% sh scriptname arguments

It is possible to debug a script by adding the -x flag to the shell executable:

% sh -x scriptname arguments

The -x option has the effect that commands are echoed prior to execution.

7.7 Relational Operators

The relational operators used depend on the command used.

For use with test, the operators =, !=, <, >, <= and >= are used when comparing strings. If two integers are being compared then the following options can be used.

-eq
equal to
-ne
not equal to
-lt
less than
-gt
greater than
-le
less than or equal to
-ge
greater than or equal to
-a
and
-o
or

For expr the following are used (angle brackets must be preceded by a back-slash to escape the normal meaning of angle brackets).

=
equal to
!=
not equal to
\<
less than
\>
greater than
\<=
less than or equal to
\>=
greater than or equal to

Help: For more information see expr(1) and test(1). See the following sections on the if statement and File Operations for examples of use of test.

7.8 The if Command

The if command takes the general form:

if test condition
then
   commands to do if condition returns true
else
   commands to do if condition returns false
fi

For example:

if test `who | wc -l` -ge 1
then
   echo Users still logged in. Do not shut down.
else
   echo All clear to shut down.
fi

and

#! /bin/sh
# sh script that returns a message depending on the date
month=$1
day=$2
if test $month = April -a $day -eq 1
then
   echo You\'ll receive twice your salary this month\!\!
else
   echo Good Morning
fi

7.9 File Operations

Using the if with test, filenames can be tested for the following:

-d filename
true if filename exists and is a directory
-e filename
true if filename exists
-f filename
true if filename is a text file
-r filename
true if filename is readable
-w filename
true if filename is writable
-x filename
true if filename is executable
-s filename
true if filename is not empty

For example, the following shell script, listdir, will list a directory, but first will test to see if the given file is a directory.

#!/bin/sh
# Bourne Shell Script listdir - lists a directory only
file=$1
if test ! -s $file
then
   echo $file does not exist.
elif test -d $file
then
   ls -l $file | more
else
   echo $file either does not exist or is not a directory.
fi

The command listdir progs will list all the files in progs or print progs either does not exist or is not a directory depending on the status of progs.

7.10 The case Command

The equivalent of the C shell switch command is the case command.

#!/bin/sh
# Script to extract the hour from the date command
# and give the user an appropriate greeting
time=`date +"%H"`
case $time in
#  The general layout is  pattern) command(s)  each separated by ;;
   "13") echo It\'s lunchtime.
;;
   "17") echo It\'s teatime.
;;
#  For a default case value use   *) command (s)
   *) echo It\'s coffee time.
;;
esac

7.11 The for Command

The for command take the general form:

for loop variable in list of items
do
   body of loop statements
done

An example of creating backup files for each file in a list given to the script:

#!/bin/sh
for i in $*
do
   cp $i $i.bkp
done

7.12 The while Command

The while command takes the general form:

while condition
   body of while statements
end

If the initial command returns false, the body of the loop is never executed. The following example could be run in the background whilst waiting for say, an FTP (File Transfer Protocol) download to finish, ie. the while loop will test the existence of the file every 30 seconds and notify the user when it exists.

#!/bin/sh
# while example
while test ! -s $1
do
   sleep 30
   done
echo -n $1 exists now


contents | previous | next | documentation directory