10. Some Basics of Shell Programming
As described in chapter 4, a shell is a command language interface to the UNIX operating system. But a shell can also be used as a programming language. You might write a shell script to make a complicated sequence of commands easy to execute or even use such a script as a substitute for a program in a more conventional programming language. The Bourne shell is the one most used for shell programming and it will be described in this section. When you call a shell script from the C shell, and #!/bin/sh is the first line of the file, it is the Bourne shell that executes the script. Note carefully, then, that any shell built-in commands you use in a shell script must be those for the Bourne shell. For a description of the Bourne shell, see
     man sh 
This chapter does not try to teach you to write shell scripts.  Its
purpose is to give you a basic understanding of the Bourne shell's
capabilities as a programming language.
     sh filename [arg1 arg2 ... argn] 
A shell script may also be executed by name if the file containing
the shell commands has read and execute permission (see chapter 5).
If file ``do_it'' contains the shell commands and has such
permissions, then the previous example is equivalent to:
     do_it [arg1 arg2 ... argn] 
In this case, executing a shell script works the same as executing a
program.  Remember that its first line should be #!/bin/sh to be sure
the Bourne shell is the command interpreter that reads the script.
     ls -l | wc -l 
If you were to create a file called ``countf'' that contained this
line (and with the correct read and execute permissions), you could
then count the number of files simply by typing:
     countf 
Any number of commands can be included in a file to create shell
scripts of any complexity.  For more than simple scripts, though, it
is usually necessary to use shell variables and to make use of
special shell programming commands.
     x
     x1
     abc_xyz
Shell variables can be assigned values like this:
     x=file1
     x1=/usr/man/man1/sh.1
     abc_xyz=4759300
Notice that there are no spaces before or after the equals-sign.  The
value will be substituted for the shell variable name if the name is
preceded by a $.  For example,
     echo $x1 
would echo
     /usr/man/man1/sh.1
Several special shell variables are predefined.  Some useful ones are
     $#, $*, $?, and $$.
Arguments can be passed to a shell script.  These arguments can be
accessed inside the script by using the shell variables $1, $2,...,$n
for positional parameter 1,2,...,n.  The filename of the shell script
itself is $0.  The number of such arguments is $#.  For example, if
file do_it is a shell script and it is called by giving the command
     do_it xyz
then $0 has the value do_it, $1 has the value xyz, and $# has the
value 1.
$* is a variable containing all the arguments (except for $0) and is often used for passing all the arguments to another program or script.
$? is the exit status of the program most recently executed in the shell script. Its value is 0 for successful completion. This variable is useful for error handling (see section 10.6).
$$ is the process id of the executing shell and is useful for creating unique filenames. For example,
     cat $1 $2 >> tempfile.$$ 
concatenates the files passed as parameters 1 and 2, appending them
to a file called tempfile.31264 (assuming the process id is 31264).
if
The if command performs a conditional branch. It takes the form
     if command-list1 
     then
     command-list2
     else
     command-list3
     fi
A  command-list
 is one or more commands.  You can put more than one command on a
line, but if you do so, separate them by semicolons.  If the last
command of  command-list1
 has exit status 0, then  command-list2
 is executed.  But if the exit status is nonzero, then 
command-list3
 is executed.
for
The for command provides a looping construct of the form
     for shell-variable in word-list
     do command-list
     done
The shell variable is set to the first word in  word-list
 and then  command-list
 is executed.  The shell variable is then set to the next word in
 word-list
 and the process continues until  word-list
 is exhausted.  A common use of for-loops is to perform
several commands on all (or a subset) of the files in your directory.
For example, to print all the files in your directory, you could use
     for i in *
     do echo printing file $i
     lpr $i
     done
In this case, * would expand to a list of all filenames in your
directory,  i
 would be set to each filename in turn, and $i
would then substitute the filename for i (in the  echo
and lpr commands).
while
The while command provides a slightly different looping construct:
     while command-list1
     do  command-list2
     done
While the exit status of the last command in  command-list1
 is 0,  command-list2
 is executed.
     test $x -eq 5 
If $x
 is equal to 5,  test
 returns true.
Other useful tests include
     test -s file    (true if file exists and has a size larger than 0)
     test -w file    (true if file exists and is writable)
     test -z string  (true if the length of string is 0)
     test string1 != string2
                     (true if string1 and string2 are not identical)
The  test
 command is often used with the flow-control constructs described
above.  Here is an example of  test
 used with the  if
 command:
     if test "$1" = ""            (or if ["$1" = ""] )
     then
     echo usage: myname  xxxx
     exit 1
     fi
This tests to see if the command line contains an argument ($1).  If
it does not ($1 is null), then  echo
 prints a message.
A complete list of test operators can be found in the man page for test.
For example,
     grep $1 phonelist
     if test $? -ne 0
     then
             echo I have no phone number for $1
     fi 
will run a program (grep) and examine the exit status to determine if
the program ran properly.
     trap 'rm tmp.*; exit' 2 
The interrupt signal is signal 2, and two commands will be executed
when an interrupt is received (rm tmp.*
 and  exit).  You can make a shell script continue to run
after logout by having it ignore the hangup signal (signal 1).  The
command
     trap ' ' 1
allows shell procedures to continue after a hangup (logout) signal.
     where=`pwd` 
will assign the string describing the current working directory (the
results of the  pwd
 command) to the shell variable  where.
Here is a more complicated example:
     for i in `ls -t *.f`
     do f77 $i
             a.out >output
             cat $i output | lpr -P$1
             rm a.out output
     done
In this case, the shell script executes a series of commands for each
file that ends with ``.f'' (all Fortran programs).  The  `ls  -t
*.f`
 is executed and expands into all filenames ending with ``.f'',
sorted by time, most recent to oldest.  Each is compiled and
executed.  Then the source file and output file are sent to the
printer identified by the first argument ($1) passed to the shell
script.  Then these files are deleted.
To merge standard output (file descriptor 1) and standard error output (file descriptor 2), then redirect them to another file, use this notation:
     command >file 2>&1
Another method of redirecting input in shell scripts allows a command
to read its input from the shell script itself without using
temporary files.  For instance, to run the editor  ed
 to change all x's in a file to z's, you could create a temporary
file of  ed
 commands, then read it to perform those commands, and finally
delete it, like this:
     echo "1,$s/x/z/g" >edtmp.$$
     echo "w"     >>edtmp.$$
     echo  "q"    >>edtmp.$$
     ed  filename  <edtmp.$$
     rm  edtmp.$$
     echo  "x's  changed  to  z's" 
The same thing can be accomplished without a temporary file by using
the  <<  symbol and a unique string, like this:
     ed filename <<%
     1,$s/x/z/g
     w
     q
     %
     echo  "x's  changed  to  z's" 
The << symbol redirects the standard input of the command to be right
here in the shell script, beginning from the next line and continuing
up to the line that matches the string following the << (in this case
 %).  The terminating string must be on a line by itself.  The
string is arbitrary: for example,  <<EOF
 will read up to a line that consists of the string EOF.
     sh -v do_it
     sh -x do_it 
To turn on both flags, use  -vx.