CHAPTER 5     SCRIPTS & JOBS

Besides being a command interpreter, a shell is also a programming language which allows you to write high-level programs called shell scripts to perform specific tasks.

A shell script file, say myscript, can be executed in 2 ways :

1. Start a new shell with myscript as the argument.

For example:

     % sh myscript <enter>      -    for  a  Bourne shell script
     % csh myscript <enter>     -  for a C shell script
2. First set the script file to be readable and executable, and then invoke the script as a command. For example,
     % chmod u+rx myscript <enter>
     % myscript <enter>
5.1 Shell Programming

In general, a shell supports the following 2 functions:

1. Defining and dropping shell variables

2. Executing conditional and iterative constructs

5.1.1 Shell Variables

There are two types of shell variables - global and local. Global variables (the environment list) can be accessed from the shell and its initiated processes or programs. On the other hand, local variables can only be accessed from the shell. For Bourne and Korn shells, global variables are defined from the local ones by using the export command. For C shell, global variables are defined and dropped by using the setenv and unsetenv commands.

When a shell is started, some useful shell variables such as path, home, term, etc. will automatically be defined.

For example, commands for setting the local variable workdir with the value /tmp are as follows:

     $ workdir=/tmp  <enter>       - for Bourne shell
     % set workdir=/tmp  <enter>   - for C shell
You can then use the expression $workdir (a dollar sign in front of the shell variable) to retrieve its assigned value.
     % cd $workdir ; pwd  <enter>
     /tmp
     % _
The commands for listing out the global and local variables which have been defined are:
                        Global                   Local
Bourne/Korn Shell       printenv, set            set
C Shell                 printenv                 set
Shell variables which are commonly used in shell scripts are given below:-
Bourne/Korn shell    C shell             Purpose
     $#              $#argv              Number of arguments
     $0              $0                  Command name
     $1,$2,...       $1,$2,...           First argument, second
                       $argv[n]            argument, ...
     $*              $*, $argv[*]        All arguments
     $@

     $?              $status             Return code from the last command

     $$              $$                  Process number of the current command
5.1.2 Conditional and iterative constructs

The syntax of the various constructs for the different shells are highlighted in this section. For more details, please refer to the on-line manual pages for the Bourne, Korn and C shells.

1. A conditional expression can be evaluated to give a result which assumes a value of either true or false.

     a. Bourne shell -
       test, expr, [ -r file ], [ 3 -ne 4 ],
       [ str1 != str2 ], [ condition ] etc.,
     b. C shell -
       -r file, ( 3 == 3 ), ( str1 != str2 ),
       ( condition ) etc.,
2. A conditional construct can be used to execute conditionally different commands according to the expression evaluation results.
     a. Bourne shell -
       if [ -r file ], if ... then ... elif ...
       else ... fi, case ... esac
     b. C shell -
       if ( -r file ) then, if ... then ... else if
       ... else ... endif, switch ... endsw
3. A looping construct can be used to execute a group of commands for a certain number of times or until certain conditions are met.
     a. Bourne shell -
       for do ... done, xargs -l, until do ... done,
       while do ... done

     b. C shell -
       foreach ... end, xargs -l, repeat n command,
       while ... end
Examples:
     Bourne/Korn shell            C shell
1. To examine whether string comparison is case-sensitive:
     test "abc" = "ABC"           test "abc" = "ABC"
     if [ $? ]                    if ( $status )then
     then                            echo case-sensitive
        echo case-sensitive       endif
     fi
2. To check whether variable 'a' is equal to variable 'b':
     if expr $a = $b              if ( $a == $b ) then
     then                            echo a = b
        echo a = b                endif
     fi
3. To find out what commands are included in the .login file if it exists:
     if [ -r .login ]             if (-r .login) then
     then                            cat .login
        cat .login                endif
     fi
4. To examine if the variable char is 'a', 'b' or any other character:
     case $char in                switch ( $char )
        'a')                         case 'a':
           echo a                       echo a
           ;;                           breaksw
        'b')                         case 'b':
           echo b                       echo b
           ;;                           breaksw
        *)                           default:
           echo not a or b              echo not a or b
           ;;                           breaksw
     esac                         endsw
5. To 'cat' all files with 'abc' as the first 3 characters of their

filenames in the current directory:

     for file in abc*             foreach file ( abc* )
     do                              cat file
        cat file                  end
     done
6. To echo the numbers 1 to 12 on separate lines on the screen:
     mon=1                        set mon=1
     while [ $mon -le 12 ]        while ( $mon < 12 )
     do                              echo $mon
        echo $mon                    @ mon += 1
        mon=`expr $mon + 1`       end
     done
7. To check the file type of all the files in the current directory by

using the file command.

ls -aC1 |xargs -l file
8. To repeat an echo command (echo YES) for 10 times:
     count=0                      repeat 10 echo YES
     until [ $count -eq 10 ]
     do
        echo YES
        count=`expr $count + 1`
     done
5.1.3 The eval command

The eval command can be used to execute a given string as it is a shell command.

For example:

5.2 Important Script Files

1. The .login C shell script file kept under your home directory is executed every time when you login the system. It usually contains the commands for defining the default system settings. However, you may include other additional commands, as required.

For example, a .login file may contain the following lines:-

     umask 077
     set path=(/bin /usr/bin $HOME/bin $HOME)
The first line sets the default access permission for your files and directory with a value which will enforce 'tight security' for all newly created files. The second line defines a secure path for the shell to search for commands or programs.

2. The .cshrc file includes those C shell commands which will be executed whenever a C shell is started.

For example, the .cshrc file may include the following command to specify that a VT100 terminal/emulation is used.

     setenv TERM vt100
3. The .profile file is the startup file for Bourne/Korn shell.

The following lines included in the file will give similar protection as the .login file given above.

     umask 077
     PATH=/bin:/usr/bin:$HOME/bin:$HOME:
     export PATH
You can define shell functions in Bourne and Korn shells instead of using aliases as in the C shell.

5.3 A sample Bourne Shell Script File

#!/bin/sh
#    A Bourne Script file for calculating the total
#       number of bytes of files under a directory
#
if [ $# -ne 1 ]                         ## the directory name
then
   echo Usage is : $0 directory_name
fi
count=`expr 0`                          ## set total to 0
for file in $1/*                        ## for each file under 1
do
   if [ -f $file ]                   ## for a non-directoy file,
   then                              ## put attribute line into x
      x=`/bin/ls -l $file`
      if test $? -ne 0
      then
         echo Regular file $file not included
         continue
      fi
   elif [ -d $file ]                   ## for a directoy file,
   then                                ## put attribute line into x 
      x=`/bin/ls -ld $file` 
      if test $? -ne 0
      then
         echo Directory file $file not included
         continue
      fi
      y=`$0 $file`                             ## byte total within $file
      if test $? -eq 0
      then
         count=`expr $y + $count`
      fi
   else
      echo File $file not included in the total
      continue
   fi
   x=`echo $x |cut -f4 -d" "`
   count=`expr $x + $count`                    ## add file size
done
echo $count                                    ## return byte total
5.4 The alternative C Shell Script
#!/bin/csh -f
set nonomatch                                    ## ignore empty directory
if ( $#argv != 1) then                           ## 1 directory name
   echo Usage is: $0 directory_name
   exit 1
endif

@ count = 0                                      ## set total to 0
foreach file ($1/*)                              ## for each file in $1
   if ( -f $file ) then
#   for a non-directory, put attribute line into x
   set x = `/bin/ls -l $file`
   if ( $status ) then
      echo Regular file $file not included
      continue
   endif
#   for a directory, put attribute line into x
   else if ( -d $file ) then
   set x = `/bin/ls -ld $file`
   if ( $status ) then
      echo Directory file $file not included
      continue
   endif
   set y = `$0 $file`                            ## Store the total to y
   if ( ! $status )   @ count = $count + $y
   else
   echo File $file not included
   continue
   endif
   @ count = $count + $x[4]                 
#   add x[4] to count where x[4] is the 4th field
end
#   return total bytes for the directory $1
echo $count
5.5 Jobs

Your login process (shell) and its child processes can send messages to the same terminal, which will be called hereafter the control terminal. However, there are times when you do not have to know the messages sent from a process to the control terminal. Thus, you can explicitly disconnect the process from its control terminal temporarily or permanently. For permanently disconnected processes and the processes that started off with no control terminal (i.e. scheduled jobs submitted using the at command), they cannot re-gain access to a control terminal. Hence, the processes will remain in the background until their termination. In UNIX, such background processes are generally called jobs.

5.5.1 Foreground and Background Processes

UNIX distinguishes between foreground and background programs. This feature allows you to run several programs from the terminal at the same time. When a program is running in the foreground, all keyboard input will be sent to the program's standard input unless you have redirected it. Thus, you cannot do anything else until the program finishes. On the other hand, if a program is running in the background, it is disconnected from the keyboard. Subsequent keyboard input will reach the UNIX shell and is interpreted as a command. Therefore, you can run many programs simultaneously in the background but only one program at a time in the foreground.

A number of built-in commands are provided by the C and Korn shells for job control :

   Ctrl/Z      -     switch current process to background
   or  bg                
   fg          -     switch a background process to foreground
   jobs        -     list all background jobs of the current logon sessions
   kill        -     kill processes by job numbers
5.5.2 Creating Background Processes from Shell

To run a program in the background, type an ampersand (&) at the end of the command line.

Example : Run the program myprogram in the background.

   $ myprogram &  <enter>      - for Bourne/Korn shell
   % myprogram &  <enter>   - for C shell
   [1] 11111   myprogram
The job number is printed in brackets ( [ ] ) followed by the process identification number (PID) for the command. It then prompts you for a new command. Entering the jobs command will produce a short report describing all the programs you are executing in the background. For example :
   % myprogram &  <enter>      - for C shell
   [1] 11111   myprogram
   % jobs  <enter>     
   [1]   + Running   myprogram
In C and Korn shells, you can also run a program, say myscript, as a background job with the following steps :-
   % myscript <enter>
   ^Z                - Ctrl/Z pressed
   Stopped
   % jobs <enter>
   [1]   Stopped     myscript
   % bg <enter>
   [1] 12345         myscript
Ctrl/Z is used to suspend a job running in the foreground. This stops the program but does not terminate it.  Entering the background command, bg, lets a stopped program continue execution in the background.

5.5.3 Examining Background Processes

The jobs command can only be used to list out the background processes which are created and disconnected from the control terminal temporarily by the C shell.

To find out all of your background processes, you have to first get a list of all processes by using the ps command (with the all processes option) and then extract the list of your processes from the long list produced by using the grep command. You may have to use ps -ef instead of ps -aux for different versions of UNIX for the following examples.

For example:

  % ps -aux |grep $USER  |more  <enter>
  USER       PID %CPU %MEM  SZ RSS TT STAT TIME  COMMAND
  h9512345 11058  0.0  0.0  80   0 ?  TW   0:00  /usr/ucb/man
  h9512345 19283  0.0  0.0 300  56 ?  IW   0:00  mail
  h9512345 20000  0.0  0.2 364 124 05 S    0:00  -csh (csh)
  .  .  .
The status field STAT can be (R) for Running, (T) for Stopped, (I) for Idle, (S) for Sleeping, (Z) for Killed but not yet removed, (W) for Waiting and (TW) for Stopped and Waiting. TT is the control terminal field where (?) implies a permanently disconnected/background process.

5.5.4 Switching Between Processes

In C and Korn shells you can switch between processes which are not disconnected permanently from the control terminal. To bring back a process from background to foreground, use the fg command. If you have more than one background job, follow fg with a job identifier - a percent sign (%) followed by the job number.

For example:

   % myscript &  <enter>
   [1] 11111   myscript
   % myprogram &gt; result  <enter>
   ^Z                            - Ctrl/Z pressed
   Stopped
   % _
   % bg <enter>
   [2] 11122   myprogram &gt; result
   % jobs <enter>
   [1] + Running     myscript
   [2] - Running     myprogram &gt; result
   % fg %1  <enter>    or          % %1  <enter>
   myscript
   _
The plus sign (+) in the report from jobs indicates which job will return to the foreground by default.

5.5.5 Terminating Processes

In a time-sharing system, you should be responsible for removing all strayed or unwanted processes which will unnecessarily tie up valuable system resources.

For example:

  % tty <enter>
  /dev/tty05
  % ps -aux |grep h9512345 |more <enter>
    USER       PID %CPU %MEM  SZ RSS TT STAT TIME  COMMAND
  h9512345 11058  0.0  0.0  80   0 ?  TW   0:00  /usr/ucb/man
  h9512345 19283  0.0  0.0 300  56 ?  IW   0:00  mail
  h9512345 20000  0.0  0.2 364 124 05 S    0:00  -csh (csh)
  .  .  .
In the above listing, it can be noted that the user h9512345 has logged into the HKUSUA system from terminal /dev/tty05 as indicated by the last line. Further, there are 2 stopped background processes (with PID's 11058 and 19283 as shown in the STAT column). These stopped processes can be killed by using the following command :
   % kill -9 11058 19283  <enter>
The -9 or -KILL is the sure-kill option which will terminate your processes specified by the PID's in the command.

You can also delete background processes by specifying their job numbers in C and Korn shells.

For example:

   % jobs <enter>
   [1] + Running     myscript
   [2] - Running     myprogram &gt; result
   % _
   % kill %2 <enter>
   % jobs <enter>
   [1] + Running     myscript
   % _
The % must be included before the job number, otherwise UNIX will interpret it as a process number. If the job is still running, use the -9 option as a last resort.
   % kill -9  %2 <enter>
The -9 option does not give the process a chance to clean up its temporary files, so do not use it unless you need to.



Computer Centre, The University of Hong Kong. Last updated September 26, 1997.