Chapter 8. The Interactive Bourne Shell

CONTENTS
  •  8.1 Startup
  •  8.2 Programming with the Bourne Shell
  •  BOURNE SHELL LAB EXERCISES

graphics/ch08.gif

8.1 Startup

If the Bourne shell is your login shell, it follows a chain of processes before you see a shell prompt.

Figure 8.1. Starting the Bourne shell.

graphics/08fig01.gif

The first process to run is called init, PID 1. It gets instructions from a file called inittab (System V), or it spawns a getty process (BSD). These processes open up the terminal ports, providing a place where standard input comes from and a place where standard output and error go, and they put a login prompt on your screen. The /bin/login program is then executed. The login program prompts for a password, encrypts and verifies the password, sets up an initial environment, and starts up the login shell, /bin/sh, the last entry in the passwd file. The sh process looks for the system file, /etc/profile, and executes its commands. It then looks in the user's home directory for an initialization file called .profile. After executing commands from .profile, the default dollar sign ($) prompt appears on your screen and the Bourne shell awaits commands.

8.1.1 The Environment

The environment of a process consists of variables, open files, the current working directory, functions, resource limits, signals, and so forth. It defines those features that are inherited from one shell to the next and the configuration for the working environment. The configuration for the user's shell is defined in the shell initialization files.

The Initialization Files. After the Bourne shell program starts up, it first checks for the system file /etc/profile. After executing the commands in that file, the initialization file, .profile, in the user's home directory, is executed. Skeleton files for initial setup can be found in /etc/skel (SVR4).

The /etc/profile File. The /etc/profile file is a systemwide initialization file set up by the system administrator to perform tasks when the user logs on. It is executed when the Bourne shell starts up. It is available to all Bourne and Korn shell users on the system and normally performs such tasks as checking the mail spooler for new mail and displaying the message of the day from the /etc/motd file. (The following examples will make more sense after you have completed this chapter.)

Example 8.1
(Sample /etc/profile) # The profile that all logins get before using their own .profile 1   trap " " 2 3 2   export LOGNAME PATH 3   if [ "$TERM" = " " ]     then         if /bin/i386         then              TERM=AT386   # Sets the terminal         else              TERM=sun         fi         export TERM     fi     # Login and -su shells get /etc/profile services.     # -rsh is given its environment in its own .profile. 4      case "$0" in        -sh | -ksh | -jsh ) 5          if [ ! -f .hushlogin ]            then               /usr/sbin/quota               # Allow the user to break the Message-Of-The-               # Day only. 6             trap "trap ' ' 2" 2 7             /bin/cat -s /etc/motd               # Message of the day displayed               trap " " 2 8             /bin/mail -E      # Checks for new mail 9             case $? in               0)                    echo "You have new mail. "               ;  ;               2)                    echo "You have mail. "                    ;;               esac            fi        esac 10     umask 022 11     trap 2 3 

EXPLANATION

  1. The trap command controls signals coming into this program while it is running. If signals 2 (Control-C) or 3 (Control-\) are sent while the program is in execution, those signals will be ignored.

  2. The variables LOGNAME and PATH are exported so that their values will be known in subshells started from this process.

  3. The command /bin/i386 is executed. If the exit status of the command is zero, the terminal variable, TERM, is assigned the value AT386; if not, the TERM variable is assigned sun.

  4. If the value of $0, the name of the program running the /etc/profile file, is either a login or Bourne, Korn, or job shell, the following commands will be executed.

  5. If the .hushlogin file does not exist, quota will be run to display the disk usage warnings if usage is over the quota.

  6. The trap is reset so that the user can terminate the message of the day with Control-C.

  7. After the message of the day has been displayed, the trap is reset to ignore Control-C.

  8. The mail program checks for new incoming mail.

  9. If the exit status ($?) of the mail program is 0 or 2, the message "You have new mail." or "You have mail.", respectively, is displayed.

  10. The umask command is set to determine the initial permissions of files and directories when they are created.

  11. The trap command sets signals 2 and 3 back to their defaults; i.e., to kill the program if either Control-C or Control-\ arrive.

The .profile File. The .profile file is a user-defined initialization file executed once at login and found in your home directory. It gives you the ability to customize and modify the shell environment. Environment and terminal settings are normally put here, and if a window application or database application is to be initiated, it is started here. The settings in this file will be discussed in detail as the chapter progresses, but a brief synopsis of each line in the file is explained here.

Example 8.2
(Sample .profile) 1   TERM=vt102 2   HOSTNAME='uname -n' 3   EDITOR=/usr/ucb/vi 4   PATH=/bin:/usr/ucb:/usr/bin:/usr/local:/etc:/bin:/usr/bin: 5   PS1="$HOSTNAME $ > " 6   export TERM HOSTNAME EDITOR PATH PS1 7   stty erase ^h 8   go () { cd $1; PS1='pwd'; PS1='basename $PS1'; } 9   trap '$HOME/.logout' EXIT 10  clear 

EXPLANATION

  1. The TERM variable is assigned the value of the terminal type, vt102.

  2. Because the uname n command is enclosed in backquotes, the shell will perform command substitution, i.e., the output of the command (the name of the host machine) will be assigned to the variable HOSTNAME.

  3. The EDITOR variable is assigned /usr/ucb/vi. Programs such as mail will now have this variable available when defining an editor.

  4. The PATH variable is assigned the directory entries that the shell searches in order to find a UNIX program. If, for example, you type ls, the shell will search the PATH until it finds that program in one of the listed directories. If it never finds the program, the shell will tell you so.

  5. The primary prompt is assigned the value of HOSTNAME, the machine name, and the $ and > symbols.

  6. All of the variables listed are exported. They will be known by child processes started from this shell.

  7. The stty command sets terminal options. The erase key is set to ^h, so that when you press the Backspace key, the letter typed preceding the cursor is erased.

  8. A function called go is defined. The purpose of this function is to take one argument, a directory name, cd to that directory, and set the primary prompt to the present working directory. The basename command removes all but the last entry of the path. The prompt will show you the current directory.

  9. The trap command is a signal handling command. When you exit the shell, that is, log out, the .logout file will be executed.

  10. The clear command clears the screen.

The Prompts. When used interactively, the shell prompts you for input. When you see the prompt, you know that you can start typing commands. The Bourne shell provides two prompts: the primary prompt, a dollar sign ($), and the secondary prompt, a right angle bracket symbol (>). The prompts are displayed when the shell is running interactively. You can change these prompts. The variable PS1 is the primary prompt set initially to a dollar sign ($). The primary prompt appears when you log on and the shell waits for you to type commands. The variable PS2 is the secondary prompt, initially set to the right angle bracket character. It appears if you have partially typed a command and then pressed the carriage return. You can change the primary and secondary prompts.

The Primary Prompt. The dollar sign is the default primary prompt. You can change your prompt. Normally prompts are defined in .profile, the user initialization file.

Example 8.3
1   $ PS1="'uname -n > '" 2   chargers > 

EXPLANATION

  1. The default primary prompt is a dollar sign ($). The PS1 prompt is being reset to the name of the machine (uname n) and a > symbol. (Don't confuse backquotes and single quotes.)

  2. The new prompt is displayed.

The Secondary Prompt. The PS2 prompt is the secondary prompt. Its value is displayed to standard error, which is the screen by default. This prompt appears when you have not completed a command and have pressed the carriage return.

Example 8.4
1   $ echo "Hello 2   > there" 3   Hello     there 4   $ 5   $ PS2=" > " 6   $ echo 'Hi 7    >      >      > there'     Hi     there     $ 

EXPLANATION

  1. The double quotes must be matched after the string "Hello.

  2. When a newline is entered, the secondary prompt appears. Until the closing double quotes are entered, the secondary prompt will be displayed.

  3. The output of the echo command is displayed.

  4. The primary prompt is displayed.

  5. The secondary prompt is reset.

  6. The single quote must be matched after the string 'Hi.

  7. When a newline is entered, the new secondary prompt appears. Until the closing single quote is entered, the secondary prompt will be displayed.

The Search Path. The path variable is used by the Bourne shell to locate commands typed at the command line. The path is a colon-separated list of directories used by the shell when searching for commands. The search is from left to right. The dot at the end of the path represents the current working directory. If the command is not found in any of the directories listed in the path, the Bourne shell sends to standard error the message filename: not found. It is recommended that the path be set in the .profile file.

If the dot is not included in the path and you are executing a command or script from the current working directory, the name of the script must be preceded with a ./, such as ./program_name, so that shell can find the program.

Example 8.5
(Printing the PATH) 1   $ echo $PATH /home/gsa12/bin:/usr/ucb:/usr/bin:/usr/local/bin:/usr/bin:/usr/local/bin:. (Setting the PATH) 2   $ PATH=$HOME:/usr/ucb:/usr:/usr/bin:/usr/local/bin:. 3   $ export PATH 

EXPLANATION

  1. By echoing $PATH, the value of the PATH variable is displayed. The path consists of a list of colon-separated elements and is searched from left to right. The dot at the end of the path represents the user's current working directory.

  2. To set the path, a list of colon-separated directories are assigned to the PATH variable.

  3. By exporting the path, child processes will have access to it.

The hash Command. The hash command controls the internal hash table used by the shell to improve efficiency in searching for commands. Instead of searching the path each time a command is entered, the first time you type a command, the shell uses the search path to find the command, and then stores it in a table in the shell's memory. The next time you use the same command, the shell uses the hash table to find it. This makes it much faster to access a command than having to search the complete path. If you know that you will be using a command often, you can add the command to the hash table. You can also remove commands from the table. The output of the hash command displays both the number of times the shell has used the table to find a command (hits) and the relative cost (cost) of looking up the command, that is, how far down the search path it had to go before it found the command. The hash command with the r option clears the hash table.

Example 8.6
1   $ hash     hits  cost   command     3     8      /usr/bin/date     1     8      /usr/bin/who     1     8      /usr/bin/ls 2   $ hash vi     3     8      /usr/bin/date     1     8      /usr/bin/who     1     8      /usr/bin/ls     0     6      /usr/ucb/vi 3   $ hash  r 

EXPLANATION

  1. The hash command displays the commands currently stored in the internal hash table. The shell will not have to search the search path to find the commands listed when they are entered at the command line. This saves time. Otherwise, the shell has to go out to the disk to search the path. When you type a new command, the shell will search the path first, and then place it on the hash table. The next time you use that command, the shell finds it in memory.

  2. The hash command can take arguments; the names of commands you want to guarantee get stored on the hash table ahead of time.

  3. The hash command with the r option clears the hash table.

The dot Command. The dot command is a built-in Bourne shell command. It takes a script name as an argument.The script will be executed in the environment of the current shell; that is, a child process will not be started. All variables set in the script will become part of the current shell's environment. Likewise, all variables set in the current shell will become part of the script's environment. The dot command is normally used to re-execute the .profile file if it has been modified. For example, if one of the settings, such as the EDITOR or TERM variable, has been changed since you logged on, you can use the dot command to re-execute the .profile without logging out and then logging back in.

Example 8.7
$ . .profile 

EXPLANATION

The dot command executes the initialization file, .profile, within this shell. Local and global variables are redefined within this shell. The dot command makes it unnecessary to log out and then log back in again.[1]

8.1.2 The Command Line

After logging on, the shell displays its primary prompt, a dollar sign, by default. The shell is your command interpreter. When the shell is running interactively, it reads commands from the terminal and breaks the command line into words. A command line consists of one or more words (tokens), separated by whitespace (blanks and/or tabs), and terminated with a newline, which is generated by pressing Enter. The first word is the command and subsequent words are the command's arguments. The command may be a UNIX executable program such as ls or pwd, a built-in command such as cd or test, or a shell script. The command may contain special characters, called metacharacters, that the shell must interpret while parsing the command line. If a command line is long and you want to continue typing on the next line, the backslash character, followed by a newline, will allow you to continue typing on the next line. The secondary prompt will appear until the command line is terminated.

The Exit Status. After a command or program terminates, it returns an exit status to the parent process. The exit status is a number between 0 and 255. By convention, when a program exits, if the status returned is zero, the command was successful in its execution. When the exit status is nonzero, the command failed in some way. The shell status variable, ?, is set to the value of the exit status of the last command that was executed. Success or failure of a program is determined by the programmer who wrote the program.

Example 8.8
1   $ grep "john" /etc/passwd     john:MgVyBsZJavd16s:9496:40:John Doe:/home/falcon/john:/bin/sh 2   $ echo $?     0 3   $ grep "nicky" /etc/passwd 4   $ echo $?     1 5   $ grep "scott" /etc/passsswd     grep: /etc/passsswd: No such file or directory 6   $ echo $?     2 

EXPLANATION

  1. The grep program searches for the pattern john in the /etc/passwd file and is successful. The line from /etc/passwd is displayed.

  2. The ? variable is set to the exit value of the grep command. Zero indicates success.

  3. The grep program cannot find user nicky in the /etc/passwd file.

  4. If the grep program cannot find the pattern, it returns an exit status of 1.

  5. The grep fails because the /etc/passsswd file cannot be opened.

  6. If grep cannot find the file, it returns an exit status of 2.

Multiple Commands at the Command Line. A command line can consist of multiple commands. Each command is separated by a semicolon, and the command line is terminated with a newline.

Example 8.9
$ ls; pwd; date 

EXPLANATION

The commands are executed from left to right, one after the other, until the newline is reached.

Grouping Commands. Commands may also be grouped so that all of the output is either piped to another command or redirected to a file.

Example 8.10
$ ( ls ; pwd; date ) > outputfile 

EXPLANATION

The output of each of the commands is sent to the file called outputfile. The spaces inside the parentheses are necessary.

Conditional Execution of Commands. With conditional execution, two command strings are separated by the special metacharacters, double ampersands (&&) and double vertical bars (||). The command on the right of either of these metacharacters will or will not be executed based on the exit condition of the command on the left.

Example 8.11
$ cc prgm1.c  o prgm1 && prgm1 

EXPLANATION

If the first command is successful (has a zero exit status), the command after the && is executed; i.e., if the cc program can successfully compile prgm1.c, the resulting executable program, prgm1, will be executed.

Example 8.12
$ cc prog.c >& err || mail bob < err 

EXPLANATION

If the first command fails (has a nonzero exit status), the command after the || is executed; i.e., if the cc program cannot compile prog.c, the errors are sent to a file called err, and user bob will be mailed the err file.

Commands in the Background. Normally, when you execute a command, it runs in the foreground, and the prompt does not reappear until the command has completed execution. It is not always convenient to wait for the command to complete. By placing an ampersand (&) at the end of the command line, the shell will return the shell prompt immediately and execute the command in the background concurrently. You do not have to wait to start up another command. The output from a background job will be sent to the screen as it processes. Therefore, if you intend to run a command in the background, the output of that command might be redirected to a file or piped to another device, such as a printer, so that the output does not interfere with what you are doing.

The $! variable contains the PID number of the last job put in the background.

Example 8.13
1   $ man sh | lp& 2   [1] 1557 3   $ kill -9 $! 

EXPLANATION

  1. The output of the man command (the manual pages for the sh command) is piped to the printer. The ampersand at the end of the command line puts the job in the background.

  2. There are two numbers that appear on the screen: the number in square brackets indicates that this is the first job to be placed in the background; the second number is the PID, or the process identification number of this job.

  3. The shell prompt appears immediately. While your program is running in the background, the shell is waiting for another command in the foreground.

  4. The ! variable evaluates to the PID of the job most recently put in the background. If you get it in time, you will kill this job before it goes to the print queue.

8.1.3 Metacharacters (Wildcards)

Metacharacters are special characters used to represent something other than themselves. Shell metacharacters are called wildcards. Table 8.1 lists metacharacters and what they do.

Table 8.1. Shell Metacharacters
Metacharacter Meaning
\ Literally interprets the following character.
& Processes in the background.
; Separates commands.
$ Substitutes variables.
? Matches for a single character.
[abc] Matches for one character from a set of characters.
[!abc] Matches for one character not from the set of characters.
* Matches for zero or more characters.
(cmds) Executes commands in a subshell.
{cmds} Executes commands in current shell.

8.1.4 Filename Substitution

When evaluating the command line, the shell uses metacharacters to abbreviate filenames or pathnames that match a certain set of characters. The filename substitution metacharacters listed in Table 8.2 are expanded into an alphabetically listed set of filenames. The process of expanding the metacharacter into filenames is also called filename substitution, or globbing. If a metacharacter is used and there is no filename that matches it, the shell treats the metacharacter as a literal character.

Table 8.2. Shell Metacharacters and Filename Substitution
Metacharacter Meaning
* Matches zero or more characters.
? Matches exactly one character.
[abc] Matches one character in the set a, b, or c.
[a z] Matches one character in the range from a to z.
[! a z] Matches one character not in the range from a to z.
\ Escapes or disables the metacharacter.

The Asterisk. The asterisk is a wildcard that matches for zero or more of any characters in a filename.

Example 8.14
1   $ ls  *     abc abc1 abc122 abc123 abc2 file1 file1.bak file2 file2.bak none     nonsense nobody nothing nowhere one 2   $ ls  *.bak     file1.bak file2.bak 3   $ echo a*     ab abc1 abc122 abc123 abc2 

EXPLANATION

  1. The asterisk expands to all of the files in the present working directory. All of the files are passed as arguments to is and displayed.

  2. All files starting with zero or more characters and ending with .bak are matched and listed.

  3. All files starting with a, followed by zero or more characters, are matched and passed as arguments to the echo command.

The Question Mark. The question mark represents a single character in a filename. When a filename contains one or more question marks, the shell performs filename substitution by replacing the question mark with the character it matches in the filename.

Example 8.15
1   $ ls     abc  abc122  abc2  file1.bak  file2.bak  nonsense  nothing  one     abc1  abc123  file1  file2  none  noone  nowhere 2   $ ls a?c?     abc1 abc2 3   $ ls ??     ?? not found 4   $ echo  abc???     abc122 abc123 5   $ echo ??     ?? 

EXPLANATION

  1. The files in the current directory are listed.

  2. Filenames starting with a, followed by a single character, followed by c and a single character, are matched and listed.

  3. Filenames containing exactly two characters are listed if found. Since there are no two-character files, the question marks are treated as a literal filename.

  4. Filenames starting with abc and followed by exactly three characters are expanded and displayed by the echo command.

  5. There are no files in the directory that contain exactly two characters. The shell treats the question mark as a literal question mark if it cannot find a match.

The Square Brackets. The brackets are used to match filenames containing one character in a set or range of characters.

Example 8.16
1   $ ls     abc  abc122 abc2 file1.bak file2.bak nonsense nothing     one  abc1 abc123 file1 file2 none noone nowhere 2   $ ls abc[123]     abc1  abc2 3   $ ls abc[1 3]     abc1  abc2 4   $ ls [a z][a z][a z]     abc one 5   $ ls [!f z] ???     abc1  abc2 6   $ ls abc12[23]     abc122 abc123 

EXPLANATION

  1. All of the files in the present working directory are listed.

  2. All filenames containing four characters are matched and listed if the filename starts with abc, followed by 1, 2, or 3. Only one character from the set in the brackets is matched.

  3. All filenames containing four characters are matched and listed if the filename starts with abc and is followed by a number in the range from 1 to 3.

  4. All filenames containing three characters are matched and listed, if the filename contains exactly three lowercase alphabetic characters.

  5. All filenames containing four characters are listed if the first character is not a lowercase letter between f and z ([!f z], followed by three of any character (e.g., ???).

  6. Files are listed if the filenames contain abc12 followed by 2 or 3.

Escaping Metacharacters. To use a metacharacter as a literal character, the backslash may be used to prevent the metacharacter from being interpreted.

Example 8.17
1   $ ls     abc file1 youx 2   $ echo How are you?     How are youx 3   $ echo How are you\?     How are you? 4   $ echo  When does this line \     > ever end\?     When does this line ever end? 

EXPLANATION

  1. The files in the present working directory are listed. (Note the file youx.)

  2. The shell will perform filename expansion on the ?. Any files in the current directory starting with y-o-u and followed by exactly one character are matched and substituted in the string. The filename youx will be substituted in the string to read How are youx (probably not what you wanted to happen).

  3. By preceding the question mark with a backslash, it is escaped, meaning that the shell will not try to interpret it as a wildcard.

  4. The newline is escaped by preceding it with a backslash. The secondary prompt is displayed until the string is terminated with a newline. The question mark (?) is escaped to protect it from filename expansion.

8.1.5 Variables

There are two types of variables, local and environment variables. Some variables are created by the user and others are special shell variables.

Local Variables. Local variables are given values that are known only to the shell in which they are created. Variable names must begin with an alphabetic or underscore character. The remaining characters can be alphabetic, decimal digits zero to nine, or an underscore character. Any other characters mark the termination of the variable name. When assigning a value, there can be no whitespace surrounding the equal sign. To set the variable to null, the equal sign is followed by a newline.[2]

A dollar sign is used in front of a variable to extract the value stored there.

Setting Local Variables

Example 8.18
1   $ round=world     $ echo $round     world 2   $ name="Peter Piper"     $ echo $name     Peter Piper 3   $ x=     $ echo $x 4   $ file.bak="$HOME/junk"     file.bak=/home/jody/ellie/junk: not found 

EXPLANATION

  1. The variable round is assigned the value world. When the shell encounters the dollar sign preceding a variable name, it performs variable substitution. The value of the variable is displayed.

  2. The variable name is assigned the value "Peter Piper". The quotes are needed to hide the whitespace so that the shell will not split the string into separate words when it parses the command line. The value of the variable is displayed.

  3. The variable x is not assigned a value. It will be assigned null. The null value, an empty string, is displayed.

  4. The period in the variable name is illegal. The only characters allowed in a variable name are numbers, letters, and the underscore. The shell tries to execute the string as a command.

The Scope of Local Variables. A local variable is known only to the shell in which it was created. It is not passed on to subshells. The double dollar sign variable is a special variable containing the PID of the current shell.

Example 8.19
1   $ echo $$     1313 2   $ round=world     $ echo $round     world 3   $ sh           # Start a subshell 4   $ echo $$     1326 5   $ echo $round 6   $ exit         # Exits this shell, returns to parent shell 7   $ echo $$     1313 8   $ echo $round     world 

EXPLANATION

  1. The value of the double dollar sign variable evaluates to the PID of the current shell. The PID of this shell is 1313.

  2. The local variable round is assigned the string value world, and the value of the variable is displayed.

  3. A new Bourne shell is started. This is called a subshell, or child shell.

  4. The PID of this shell is 1326. The parent shell's PID is 1313.

  5. The variable round is not defined in this shell. A blank line is printed.

  6. The exit command terminates this shell and returns to the parent shell. (Control-D will also exit this shell.)

  7. The parent shell returns. Its PID is displayed.

  8. The value of the variable round is displayed.

Setting Read-Only Variables. A read-only variable cannot be redefined or unset.

Example 8.20
1   $ name=Tom 2   $ readonly name     $ echo $name     Tom 3   $ unset name     name: readonly 4   $ name=Joe     name: readonly 

EXPLANATION

  1. The local variable name is assigned the value Tom.

  2. The variable is made readonly.

  3. A read-only variable cannot be unset.

  4. A read-only variable cannot be redefined.

Environment Variables. Environment variables are available to the shell in which they are created and any subshells or processes spawned from that shell. By convention, environment variables are capitalized. Environment variables are variables that have been exported.

The shell in which a variable is created is called the parent shell. If a new shell is started from the parent shell, it is called the child shell. Some of the environment variables, such as HOME, LOGNAME, PATH, and SHELL, are set before you log on by the /bin/login program. Normally, environment variables are defined and stored in the .profile file in the user's home directory. See Table 8.3 for a list of environment variables.

Table 8.3. Bourne Shell Environment Variables
ENV Variable Value
PATH The search path for commands.
HOME Home directory; used by cd when no directory is specified.
IFS Internal field separators, normally space, tab, and newline.
LOGNAME The user's login name.
MAIL If this parameter is set to the name of a mail file and the MAILPATH parameter is not set, the shell informs the user of the arrival of mail in the specified file.
MAILCHECK This parameter specifies how often (in seconds) the shell will check for the arrival of mail in the files specified by the MAILPATH or MAIL parameters. The default value is 600 seconds (10 minutes). If set to zero, the shell will check before issuing each primary prompt.
MAILPATH A colon-separated list of filenames. If this parameter is set, the shell informs the user of the arrival of mail in any of the specified files. Each filename can be followed by a percent sign and a message that will be printed when the modification time changes. The default message is You have mail.
PWD Present working directory.
PS1 Primary prompt string, which is a dollar sign by default.
PS2 Secondary prompt string, which is a right angle bracket by default.
SHELL When the shell is invoked, it scans the environment for this name. The shell gives default values to PATH, PS1, PS2, MAILCHECK, and IFS. HOME and MAIL are set by login.

Setting Environment Variables. To set environment variables, the export command is used either after assigning a value or when the variable is set. (Do not use the dollar sign on a variable when exporting it.)

Example 8.21
1   $ TERM=wyse    $ export TERM 2   $ NAME= "John Smith"     $ export NAME     $ echo $NAME     John Smith 3   $ echo $$     319              # pid number for parent shell 4   $ sh                      # Start a subshell 5   $ echo $$     340              # pid number for new shell 6   $ echo $NAME     John Smith 7   $ NAME="April Jenner"     $ export NAME     $ echo $NAME     April Jenner 8   $ exit           # Exit the subshell and go back to parent shell 9   $ echo $$     319              # pid number for parent shell 10  $ echo $NAME     John Smith 

EXPLANATION

  1. The TERM variable is assigned wyse. The variable is exported. Now, processes started from this shell will inherit the variable.

  2. The variable is defined and exported to make it available to subshells started from the shell.

  3. The value of this shell's PID is printed.

  4. A new Bourne shell is started. The new shell is called the child. The original shell is its parent.

  5. The PID of the new Bourne shell is stored in the $$ variable and its value is echoed.

  6. The variable, set in the parent shell, was exported to this new shell and is displayed.

  7. The variable is reset to April Jenner. It is exported to all subshells, but will not affect the parent shell. Exported values are not propagated upward to the parent shell.

  8. This Bourne child shell is exited.

  9. The PID of the parent is displayed again.

  10. The variable NAME contains its original value. Variables retain their values when exported from parent to child shell. The child cannot change the value of a variable for its parent.

Listing Set Variables. There are two built-in commands that print the value of a variable: set and env. The set command prints all variables, local and global. The env command prints just the global variables.

Example 8.22
1   $ env     (Partial list)     LOGNAME=ellie     TERMCAP=sun cmd     USER=ellie     DISPLAY=:0.0     SHELL=/bin/sh     HOME=/home/jody/ellie     TERM=sun cmd     LD_LIBRARY_PATH=/usr/local/OW3/lib     PWD=/home/jody/ellie/perl 2   $ set     DISPLAY=:0.0     FMHOME=/usr/local/Frame 2.1X     FONTPATH=/usr/local/OW3/lib/fonts     HELPPATH=/usr/local/OW3/lib/locale:/usr/local/OW3/lib/help     HOME=/home/jody/ellie     HZ=100     IFS=     LANG=C     LD_LIBRARY_PATH=/usr/local/OW3/lib     LOGNAME=ellie     MAILCHECK=600     MANPATH=/usr/local/OW3/share/man:/usr/local/OW3/man:/     usr/local/man:/usr/local/doctools/man:/usr/man     OPTIND=1     PATH=/home/jody/ellie:/usr/local/OW3/bin:/usr/ucb:/usr/local/     doctools/bin:/usr/bin:/usr/local:/usr/etc:/etc:/usr/spool/     news/bin:/home/jody/ellie/bin:/usr/lo     PS1=$     PS2=>     PWD=/home/jody/ellie/kshprog/joke     SHELL=/bin/sh     TERM=sun cmd     TERMCAP=sun cmd:te=\E[>4h:ti=\E[>4l:tc=sun:     USER=ellie     name=Tom     place="San Francisco" 

EXPLANATION

  1. The env command lists all environment (exported) variables. These variables are, by convention, named with all uppercase letters. They are passed from the process in which they are created to any of its child processes.

  2. The set command, without options, prints all set variables, local and exported (including variables set to null).

Unsetting Variables. Both local and environment variables can be unset by using the unset command, unless the variables are set as read-only.

Example 8.23
unset name; unset TERM 

EXPLANATION

The unset command removes the variable from the shell's memory.

Printing the Values of Variables: The echo Command. The echo command prints its arguments to standard output and is used primarily in Bourne and C shells. The Korn shell has a built-in print command. There are different versions of the echo command; for example, the Berkeley (BSD) echo is different from the System V echo. Unless you specify a full pathname, you will use the built-in version of the echo command. The built-in version will reflect the version of UNIX you are using. The System V echo allows the use of numerous escape sequences, whereas the BSD version does not. Table 8.4 lists the BSD echo option and escape sequences.

Table 8.4. BSD echo Option and System V Escape Sequences
Option Meaning
BSD:
n Suppress newline at the end of a line of output.
System V:
\b Backspace.
\c Print the line without a newline.
\f Form feed.
\n Newline.
\r Return.
\t Tab.
\v Vertical tab.
\\ Backslash.
Example 8.24
1   $ echo The username is $LOGNAME.     The username is ellie. 2   $ echo "\t\tHello there\c"   # System V     Hello there$ 3   $ echo -n "Hello there"      # BSD     Hello there$ 

EXPLANATION

  1. The echo command prints its arguments to the screen. Variable substitution is performed by the shell before the echo command is executed.

  2. The System V version of the echo command supports escape sequences similar to those of the C programming language. The $ is the shell prompt.

  3. The n option to the echo command indicates the BSD version of the echo command is being used. The line is printed without the newline. The escape sequences are not supported by this version of echo.

Variable Expansion Modifiers. Variables can be tested and modified by using special modifiers. The modifier provides a shortcut conditional test to check if a variable has been set, and then assigns a value to the variable based on the outcome of the test. See Table 8.5 for a list of variable modifiers.

Table 8.5. Variable Modifiers
Modifier Value
${variable:-word} If variable is set and is non-null, substitute its value; otherwise, substitute word.
${variable:=word} If variable is set or is non-null, substitute its value; otherwise, set it to word. The value of variable is substituted permanently. Positional parameters may not be assigned in this way.
${variable:+word} If parameter is set and is non-null, substitute word; otherwise, substitute nothing.
${variable:?word} If variable is set and is non-null, substitute its value; otherwise, print word and exit from the shell. If word is omitted, the message parameter null or not set is printed.

Using the colon with any of the modifiers (-, =, +, ?) checks whether the variable is not set or is null; without the colon, a variable set to null is considered to be set.

Example 8.25
(Assigning Temporary Default Values) 1   $ fruit=peach 2   $ echo ${fruit: plum}     peach 3   $ echo ${newfruit: apple}     apple 4   $ echo $newfruit 5   $ echo $EDITOR       # More realistic example 6   $ echo ${EDITOR:-/bin/vi}     /bin/vi 7   $ echo $EDITOR 8   $ name=     $ echo ${name-Joe} 9   $ echo ${name:-Joe}     Joe 

EXPLANATION

  1. The variable fruit is assigned the value peach.

  2. The special modifier will check to see if the variable fruit has been set. If it has, the value is printed; if not, plum is substituted for fruit and its value is printed.

  3. The variable newfruit has not been set. The value apple will be temporarily substituted for newfruit.

  4. The setting was only temporary. The variable newfruit is not set.

  5. The environment variable EDITOR has not been set.

  6. The : modifier substitutes EDITOR with /bin/vi.

  7. The EDITOR was never set. Nothing prints.

  8. The variable name is set to null. By not prefixing the modifier with a colon, the variable is considered to be set, even if to null, and the new value Joe is not assigned to name.

  9. The colon causes the modifier to check that a variable is either not set or is set to null. In either case, the value Joe will be substituted for name.

Example 8.26
(Assigning Permanent Default Values) 1   $ name= 2   $ echo  ${name:=Patty}     Patty 3   $ echo $name     Patty 4   $ echo ${EDITOR:=/bin/vi}     /bin/vi 5   $ echo $EDITOR     /bin/vi 

EXPLANATION

  1. The variable name is assigned the value null.

  2. The special modifier := will check to see if the variable name has been set. If it has been set, it will not be changed; if it is either null or not set, it will be assigned the value to the right of the equal sign. Patty is assigned to name since the variable is set to null. The setting is permanent.

  3. The variable name still contains the value Patty.

  4. The value of the variable EDITOR is set to /bin/vi.

  5. The value of the variable EDITOR is displayed.

Example 8.27
(Assigning Temporary Alternate Value) 1   $ foo=grapes 2   $ echo ${foo:+pears}     pears 3   $ echo $foo     grapes     $ 

EXPLANATION

  1. The variable foo has been assigned the value grapes.

  2. The special modifier :+ will check to see if the variable has been set. If it has been set, pears will temporarily be substituted for foo; if not, null is returned.

  3. The variable foo now has its original value.

Example 8.28
(Creating Error Messages Based On Default Values) 1   $ echo ${namex:?"namex is undefined"}     namex: namex is undefined 2   $ echo ${y?}     y: parameter null or not set 

EXPLANATION

  1. The :? modifier will check to see if the variable has been set. If not, the string to the right of the ? is printed to standard error, after the name of the variable. If in a script, the script exits.

  2. If a message is not provided after the ?, the shell sends a default message to standard error.

Positional Parameters. Normally, the special built-in variables, often called positional parameters, are used in shell scripts when passing arguments from the command line, or used in functions to hold the value of arguments passed to the function. See Table 8.6. The variables are called positional parameters because they are denoted by their position on the command line. The Bourne shell allows up to nine positional parameters. The name of the shell script is stored in the $0 variable. The positional parameters can be set and reset with the set command.

Table 8.6. Positional Parameters
Positional Parameter Meaning
$0 References the name of the current shell script.
$1 $9 Denotes positional parameters 1 through 9.
$# Evaluates to the number of positional parameters.
$* Evaluates to all the positional parameters.
$@ Means the same as $*, except when double quoted.
"$*" Evaluates to "$1 $2 $3".
"$@" Evaluates to "$1" "$2" "$3".
Example 8.29
1   $ set tim bill ann fred     $ echo $*      # Prints all the positional parameters                    # tim bill ann fred. 2   $ echo $1      # Prints the first positional parameter.     tim 3   $ echo $2 $3   # Prints the second and third     bill ann       # positional parameters. 4   $ echo $#      # Prints the total number of     4              # positional parameters. 5   $ set a b c d e f g h i j k l m     $ echo  $10    # Prints the first positional parameter     a0             # followed by a zero. 6   $ echo  $*    a b c d e f g h i j k l m 7   $ set file1 file2 file3     $ echo  \$$#     $3 8   $ eval echo  \$$#     file3 

EXPLANATION

  1. The set command assigns values to positional parameters. The $* special variable contains all of the parameters set.

  2. The value of the first positional parameter, tim, is displayed.

  3. The value of the second and third parameters, bill and ann, are displayed.

  4. The $# special variable contains the number of positional parameters currently set.

  5. The set command resets all of the positional parameters. The original set is destroyed. Positional parameters cannot be numbered beyond nine. The value of the first positional parameter is printed, followed by the number zero.

  6. The $* allows you to print all of the parameters, even past nine.

  7. The positional parameters are reset to file1, file2, and file3. The dollar sign is escaped; $# is the number of arguments. The echo command displays $3.

  8. The eval command parses the command line a second time before executing the command. The first time parsed, the shell substitutes \$$# with $3, and the second time, the shell substitutes the value of $3 with file3.

Other Special Variables. The shell has special variables consisting of a single character. The dollar sign preceding the character allows you to access the value stored in the variable. See Table 8.7.

Table 8.7. Special Variables
Variable Meaning
$ The PID of the shell
The sh options currently set
? The exit value of last executed command
! The PID of the last job put in the background
Example 8.30
1   $ echo The pid of this shell is $$     The pid of this shell is 4725 2   $ echo The options for this shell are $      The options for this shell are s 3   $ grep dodo /etc/passwd     $ echo $?     1 4   $ sleep 25&     4736     $ echo $!     4736 

EXPLANATION

  1. The $ variable holds the value of the PID for this process.

  2. The variable lists all options for this interactive Bourne shell.

  3. The grep command searches for the string dodo in the /etc/passwd file. The ? variable holds the exit status of the last command executed. Since the value returned from grep is 1, grep is assumed to have failed in its search. An exit status of zero indicates a successful exit.

  4. The ! variable holds the PID number of the last command placed in the background. The & appended to the sleep command sends the command to the background.

8.1.6 Quoting

Quoting is used to protect special metacharacters from interpretation. There are three methods of quoting: the backslash, single quotes, and double quotes. The characters listed in Table 8.8 are special to the shell and must be quoted.

Table 8.8. Special Metacharacters Requiring Quotes
Metacharacter Meaning
; Command separator
& Background processing
( ) Command grouping; creates a subshell
{ } Command grouping; does not create a subshell
| Pipe
< Input redirection
> Output redirection
newline Command termination
space/tab Word delimiter
$ Variable substitution character
* [ ] ? Shell metacharacters for filename expansion

Single and double quotes must be matched. Single quotes protect special metacharacters, such as $, *, ?, |, >, and <, from interpretation. Double quotes also protect special metacharacters from being interpreted, but allow variable and command substitution characters (the dollar sign and backquotes) to be processed. Single quotes will protect double quotes and double quotes will protect single quotes.

The Bourne shell does not let you know if you have mismatched quotes. If running interactively, a secondary prompt appears when quotes are not matched. If in a shell script, the file is scanned and if the quote is not matched, the shell will attempt to match it with the next available quote; if the shell cannot match it with the next available quote, the program aborts and the message 'end of file' unexpected appears on the terminal. Quoting can be a real hassle for even the best of shell programmers! See Appendix C for shell quoting rules.

The Backslash. The backslash is used to quote (or escape) a single character from interpretation. The backslash is not interpreted if placed in single quotes. The backslash will protect the dollar sign ($), backquotes (' '), and the backslash from interpretation if enclosed in double quotes.

Example 8.31
1   $ echo Where are you going\?     Where are you going? 2   $ echo Start on this line and \     >  go to the next line.    Start on this line and go to the next line. 3   $ echo \\     \ 4   $ echo '\\'     \\ 5   $ echo '\$5.00'     \$5.00 6   $ echo  "\$5.00"     $5.00 

EXPLANATION

  1. The backslash prevents the shell from performing filename substitution on the question mark.

  2. The backslash escapes the newline, allowing the next line to become part of this line.

  3. Since the backslash itself is a special character, it prevents the backslash following it from interpretation.

  4. The backslash is not interpreted when enclosed in single quotes.

  5. All characters in single quotes are treated literally. The backslash does not serve any purpose here.

  6. When enclosed in double quotes, the backslash prevents the dollar sign from being interpreted for variable substitution.

Single Quotes. Single quotes must be matched. They protect all metacharacters from interpretation. To print a single quote, it must be enclosed in double quotes or escaped with a backslash.

Example 8.32
1   $ echo 'hi there     > how are you?     > When will this end?     > When the quote is matched     > oh'     hi there     how are you?     When will this end?     When the quote is matched     oh 2   $ echo 'Don\'t  you need $5.00?'     Don't you need $5.00? 3   $ echo 'Mother yelled, "Time to eat!"'     Mother yelled, "Time to eat!" 

EXPLANATION

  1. The single quote is not matched on the line. The Bourne shell produces a secondary prompt. It is waiting for the quote to be matched.

  2. The single quotes protect all metacharacters from interpretation. The apostrophe in Don't is escaped with a backslash. Otherwise, it would match the first quote, and the single quote at the end of the string would not have a mate. In this example, the $ and the ? are protected from the shell and will be treated as literals.

  3. The single quotes protect the double quotes in this string.

Double Quotes. Double quotes must be matched, will allow variable and command substitution, and protect any other special metacharacters from being interpreted by the shell.

Example 8.33
1   $ name=Jody 2   $ echo "Hi $name, I'm glad to meet you!"     Hi Jody, I'm glad to meet you! 3   $ echo "Hey $name, the time is 'date'"     Hey Jody, the time is Wed Dec 14 14:04:11 PST 1998 

EXPLANATION

  1. The variable name is assigned the string Jody.

  2. The double quotes surrounding the string will protect all special metacharacters from interpretation, with the exception of $ in $name. Variable substitution is performed within double quotes.

  3. Variable substitution and command substitution are both performed when enclosed within double quotes. The variable name is expanded, and the command in backquotes, date, is executed.

8.1.7 Command Substitution

Command substitution is used when assigning the output of a command to a variable or when substituting the output of a command within a string. All three shells use backquotes to perform command substitution.[3]

Example 8.34
1   $ name='nawk  F:  '{print $1}' database'     $ echo $name     Ebenezer Scrooge 2   $ set 'date' 3   $ echo $*     Fri Oct 19 09:35:21 PDT 2001 4   $ echo  $2 $6     Oct 2001 

EXPLANATION

  1. The nawk command is enclosed in backquotes. It is executed and its output is assigned to the variable name, as a string, and displayed.

  2. The set command assigns the output of the date command to positional parameters. Whitespace separates the list of words into its respective parameters.

  3. The $* variable holds all of the positional parameters. The output of the date command was stored in the $* variable. Each parameter is separated by whitespace.

  4. The second and sixth parameters are printed.

8.1.8 An Introduction to Functions

Although the Bourne shell does not have an alias mechanism for abbreviating commands, it does support functions (introduced to the shell in SVR2). Functions are used to execute a group of commands with a name. They are like scripts, only more efficient. Once defined, they become part of the shell's memory so that when the function is called, the shell does not have to read it in from the disk as it does with a file. Often functions are used to improve the modularity of a script (discussed in the programming section of this chapter). Once defined, they can be used again and again. Functions are often defined in the user's initialization file, .profile. They must be defined before they are invoked, and cannot be exported.

Defining Functions. The function name is followed by a set of empty parentheses. The definition of the function consists of a set of commands separated by semicolons and enclosed in curly braces. The last command is terminated with a semicolon. Spaces around the curly braces are required.

FORMAT

function_name () { commands ; commands; } 
Example 8.35
1   $ greet () { echo "Hello $LOGNAME, today is 'date'; } 2   $ greet     Hello ellie, today is Thu Oct 4 19:56:31 PDT  2001 

EXPLANATION

  1. The function is called greet.

  2. When the greet function is invoked, the command(s) enclosed within the curly braces are executed.

Example 8.36
1   $ fun () { pwd; ls; date; } 2   $ fun     /home/jody/ellie/prac     abc    abc123   file1.bak   none      nothing   tmp     abc1   abc2     file2       nonsense  nowhere   touch     abc122 file1    file2.bak noone       one     Sat Feb 24 11:15:48 PST 2001 3   $ welcome () { echo "Hi $1 and $2"; } 4   $ welcome tom joe     Hi tom and joe 5   $ set jane nina lizzy 6   $ echo $*     jane nina lizzy 7   $ welcome tom joe     hi tom and joe 8   $ echo  $1 $2     jane nina 

EXPLANATION

  1. The function fun is named and defined. The name is followed by a list of commands enclosed in curly braces. Each command is separated by a semicolon. A space is required after the first curly brace or you will get a syntax error. A function must be defined before it can be used.

  2. The function behaves just like a script when invoked. Each of the commands in the function definition are executed in turn.

  3. There are two positional parameters used in the function welcome. When arguments are given to the function, the positional parameters are assigned those values.

  4. The arguments to the function, tom and joe, are assigned to $1 and $2, respectively. The positional parameters in a function are private to the function and will not interfere with any used outside the function.

  5. The positional parameters are set at the command line. These variables have nothing to do with the ones set in the function.

  6. $* displays the values of the currently set positional parameters.

  7. The function welcome is called. The values assigned to the positional parameters are tom and joe.

  8. The positional variables assigned at the command line are unaffected by those set in the function.

Listing and Unsetting Functions. To list functions and their definitions, use the set command. The function and its definition will appear in the output, along with the exported and local variables. Functions and their definitions are unset with the unset command.

8.1.9 Standard I/O and Redirection

When the shell starts up, it inherits three files: stdin, stdout, and stderr. Standard input normally comes from the keyboard. Standard output and standard error normally go to the screen. There are times when you want to read input from a file or send output or error to a file. This can be accomplished by using I/O redirection. See Table 8.9 for a list of redirection operators.

Table 8.9. Redirection Operators
Redirection Operator What It Does
< Redirect input.
> Redirect output.
>> Append output.
2> Redirect error.
1>&2 Redirect output to where error is going.
2>&1 Redirect error to where output is going.
Example 8.37
1   $ tr '[A-Z]'  '[a-z]' < myfile                              # Redirect  input 2   $ ls > lsfile            # Redirect output     $ cat lsfile     dir1     dir2     file1     file2     file3 3   $ date >> lsfile         # Redirect and append otuput     $ cat lsfile     dir1     dir2     file1     file2     file3     Mon Sept 17 12:57:22 PDT 2001 4   $ cc prog.c 2> errfile   Redirect error 5   $ find .  name \*.c  print > foundit 2> /dev/null     # Redirect output to foundit, and error to /dev/null. 6   $ find .  name \*.c  print > foundit 2>&1     # Redirect output and send standard error to where output is going 7   $ echo "File needs an argument" 1>&2     # Send standard output to error 

EXPLANATION

  1. Instead of getting input from the keyboard, standard input is redirected from the file myfile to the UNIX tr command. All uppercase letters are converted to lowercase letters.

  2. Instead of sending output to the screen, the ls command redirects its output to the file lsfile.

  3. The output of the date command is redirected and appended to lsfile.

  4. The file prog.c is compiled. If the compile fails, the standard error is redirected to the file errfile. Now you can take your error file to the local guru for an explanation (of sorts)!

  5. The find command starts searching in the current working directory for filenames ending in .c, and prints the filenames to a file named foundit. Errors from the find command are sent to /dev/null.

  6. The find command starts searching in the current working directory for filenames ending in .c, and prints the filenames to a file named foundit. The errors are also sent to foundit.

  7. The echo command sends its message to standard error. Its standard output is merged with standard error.

The exec Command and Redirection. The exec command can be used to replace the current program with a new one without starting a new process. Standard output or input can be changed with the exec command without creating a subshell (see Table 8.10). If a file is opened with exec, subsequent read commands will move the file pointer down the file a line at a time until the end of the file. The file must be closed to start reading from the beginning again. However, if using UNIX utilities such as cat and sort, the operating system closes the file after each command has completed. For examples on how to use exec commands in scripts, see "Looping Commands".

Table 8.10. The exec Command
exec Command What It Does
exec ls ls will execute in place of the shell. When ls is finished, the shell in which it was started does not return.
exec < filea Open filea for reading standard input.
exec > filex Open filex for writing standard output.
exec 3< datfile Open datfile as file descriptor 3 for reading input.
sort <&3 Datfile is sorted.
exec 4>newfile Open newfile as file descriptor (fd) 4 for writing.
ls >&4 Output of ls is redirected to newfile.
exec 5<&4 Make fd 5 a copy of fd 4.
exec 3<& Close fd 3.
Example 8.38
1   $ exec date     Sun Oct 14 10:07:34  PDT 2001      <Login prompt appears if you are in your login shell > 2   $ exec > temp     $ ls     $ pwd     $ echo Hello 3   $ exec > /dev/tty 4   $ echo Hello     Hello 

EXPLANATION

  1. The exec command executes the date command in the current shell (it does not fork a child shell). Since the date command is executed in place of the current shell, when the date command exits, the shell terminates. If a Bourne shell had been started from the C shell, the Bourne shell would exit and the C shell prompt would appear. If you are in your login shell when you try this, you will be logged out. If you are working interactively in a shell window, the window exits.

  2. The exec command opens standard output for the current shell to the temp file. Output from ls, pwd, and echo will no longer go to the screen, but to temp.

  3. The exec command reopens standard output to the terminal. Now, output will go to the screen as shown in line 4.

  4. Standard output has been directed back to the terminal (/dev/tty).

Example 8.39
1   $ cat doit     pwd     echo hello     date 2   $ exec < doit     /home/jody/ellie/shell     hello     Sun Oct 14 10:07:34  PDT 2001 3   % 

EXPLANATION

  1. The contents of a file called doit are displayed.

  2. The exec command opens standard input to the file called doit. Input is read from the file instead of from the keyboard. The commands from the file doit are executed in place of the current shell. When the last command exits, so does the shell. See Figure 8.2.

    Figure 8.2. The exec command.

    graphics/08fig02.gif

  3. The Bourne shell exited when the exec command completed. The C shell prompt appeared. It was the parent shell. If you had been in your login shell when the exec finished, you would be logged out; if in a window, the window would have disappeared.

Example 8.40
1   $ exec 3> filex 2   $ who >& 3 3   $ date >& 3 4   $ exec 3>&- 5   $ exec 3< filex 6   $ cat <&3     ellie  console  Oct 7     09:53     ellie  ttyp0    Oct 7     09:54     ellie  ttyp1    Oct 7     09:54     ellie  ttyp2    Oct 11    15:42     Sun Oct 14 13:31:31  PDT 2001 7   $ exec 3<&- 8   $ date >& 3     Sun Oct 14 13:41:14  PDT 2001 

EXPLANATION

  1. File descriptor 3 (fd 3) is assigned to filex and opened for redirection of output. See Figure 8.3.

    Figure 8.3. exec and file descriptors.

    graphics/08fig03.gif

  2. The output of the who command is sent to fd 3, filex.

  3. The output of the date command is sent to fd 3; filex is already opened, so the output is appended to filex.

  4. Fd 3 is closed.

  5. The exec command opens fd 3 for reading input. Input will be redirected from filex.

  6. The cat program reads from fd 3, assigned to filex.

  7. The exec command closes fd 3. (Actually, the operating system will close the file once end of file is reached.)

  8. When attempting to send the output of the date command to fd 3, the output goes to the screen, since the file descriptor was closed.

8.1.10 Pipes

A pipe takes the output from the command on the left-hand side of the pipe symbol and sends it to the input of the command on the right-hand side of the pipe symbol. A pipeline can consist of more than one pipe.

The purpose of the next three commands is to count the number of people logged on (who), save the output of the command in a file (tmp), use the wc l to count the number of lines in the tmp file (wc l), and then remove the tmp file (i.e., find the number of people logged on). See Figures 8.4 and 8.5.

Figure 8.4. The pipe.

graphics/08fig04.gif

Figure 8.5. Multiple pipes (filter).

graphics/08fig05.gif

Example 8.41
1   $ who > tmp 2   $ wc  l tmp     4 tmp 3   $ rm tmp # Using a pipe saves disk space and time. 4   $ who | wc  l     4 5   $ du . | sort  n | sed  n '$p'     72388   /home/jody/ellie 

EXPLANATION

  1. The output of the who command is redirected to the tmp file.

  2. The wc l command displays the number of lines in tmp.

  3. The tmp file is removed.

  4. With the pipe facility, you can perform all three of the preceding steps in one step. The output of the who command is sent to an anonymous kernel buffer; the wc l command reads from the buffer and sends its output to the screen.

  5. The output of the du command, the number of disk blocks used per directory, is piped to the sort command and sorted numerically. It is then piped to the sed command, which prints the last line of the output it receives.

8.1.11 The here document and Redirecting Input

The here document accepts inline text for a program expecting input, such as mail, sort, or cat, until a user-defined terminator is reached. It is often used in shell scripts for creating menus. The command receiving the input is appended with a << symbol, followed by a user-defined word or symbol, and a newline. The next lines of text will be the lines of input to be sent to the command. The input is terminated when the user-defined word or symbol is then placed on a line by itself in the leftmost column (it cannot have spaces surrounding it). The word is used in place of Control-D to stop the program from reading input.

If the terminator is preceded by the << operator, leading tabs, and only tabs, may precede the final terminator. The user-defined terminating word or symbol must match exactly from "here" to "here." The following examples illustrate the use of the here document at the command line to demonstrate the syntax. It is much more practical to use them in scripts.

Example 8.42

graphics/08prfig01.gif

EXPLANATION

  1. The UNIX cat program will accept input until the word FINISH appears on a line by itself.

  2. A secondary prompt appears. The following text is input for the cat command. Variable substitution is performed within the here document.

  3. Command substitution, 'date', is performed within the here document.

  4. The user-defined terminator FINISH marks the end of input for the cat program. It cannot have any spaces before or after it and is on a line by itself.

  5. The output from the cat program is displayed.

  6. The shell prompt reappears.

Example 8.43
1   $ cat <<  DONE     >   Hello there     >   What's up?     >   Bye now The time is 'date'. 2   >   DONE     Hello there     What's up?     Bye now The time is Thu Feb 8 19:48:23 PST 2001.     $ 

EXPLANATION

  1. The cat program accepts input until DONE appears on a line by itself. The << operator allows the input and final terminator to be preceded by one or more tabs.

  2. The final matching terminator, DONE, is preceded by a tab. The output of the cat program is displayed on the screen.

8.2 Programming with the Bourne Shell

8.2.1 The Steps in Creating a Shell Script

A shell script is normally written in an editor and consists of commands interspersed with comments. Comments are preceded by a pound sign and consist of text used to document what is going on.

The First Line. The first line at the top left corner of the script will indicate the program that will be executing the lines in the script. This line is commonly written as

#!/bin/sh 

The #! is called a magic number and is used by the kernel to identify the program that should be interpreting the lines in the script. This line must be the top line of your script.

Comments. Comments are lines preceded by a pound sign (#) and can be on a line by themselves or on a line following a script command. They are used to document your script. It is sometimes difficult to understand what the script is supposed to do if it is not commented. Although comments are important, they are often too sparse or not used at all. Try to get used to commenting what you are doing not only for someone else, but also for yourself. Two days from now you may not recall exactly what you were trying to do.

Executable Statements and Bourne Shell Constructs. A Bourne shell program consists of a combination of UNIX commands, Bourne shell commands, programming constructs, and comments.

Making the Script Executable. When you create a file, it is not given the execute permission. You need this permission to run your script. Use the chmod command to turn on the execute permission.

Example 8.44
1   $ chmod +x myscript 2   $ ls -lF  myscript     -rwxr-xr-x    1  ellie   0 Jul  13:00 myscript* 

EXPLANATION

  1. The chmod command is used to turn on the execute permission for the user, group, and others.

  2. The output of the ls command indicates that all users have execute permission on the myscript file. The asterisk at the end of the filename also indicates that this is an executable program.

A Scripting Session. In the following example, the user will create a script in the editor. After saving the file, the execute permissions are turned on, and the script is executed. If there are errors in the program, the shell will respond immediately.

Example 8.45
(The Script) % cat greetings 1   #!/bin/sh 2   # This is the first Bourne shell program of the day.     # Scriptname: greetings     # Written by:  Barbara Born 3   echo "Hello $LOGNAME, it's nice talking to you." 4   echo "Your present working directory is 'pwd'."     echo "You are working on a machine called 'uname -n'."     echo "Here is a list of your files." 5   ls      # List files in the present working directory 6   echo  "Bye for now $LOGNAME. The time is 'date +%T'!" (The Command Line)     $ chmod +x greetings     $ greetings 3   Hello barbara, it's nice talking to you. 4   Your present working directory is /home/lion/barbara/prog     You are working on a machine called lion.     Here is a list of your files. 5   Afile      cplus   letter          prac     Answerbook cprog   library  prac1     bourne     joke    notes    perl5 6   Bye for now barbara.  The time is 18:05:07! 

EXPLANATION

  1. The first line of the script, #!/bin/sh, lets the kernel know what interpreter will execute the lines in this program, in this case the sh (Bourne shell) interpreter.

  2. The comments are nonexecutable lines preceded by a pound sign. They can be on a line by themselves or appended to a line after a command.

  3. After variable substitution is performed by the shell, the echo command displays the line on the screen.

  4. After command substitution is performed by the shell, the echo command displays the line on the screen.

  5. The ls command is executed. The comment will be ignored by the shell.

  6. The echo command displays the string enclosed within double quotes. Variables and command substitution (backquotes) are expanded when placed within double quotes. In this case, the quotes were really not necessary.

8.2.2 Reading User Input

The read command is a built-in command used to read input from the terminal or from a file (see Table 8.11). The read command takes a line of input until a newline is reached. The newline at the end of a line will be translated into a null byte when read. You can also use the read command to cause a program to stop until the user enters a carriage return. To see how the read command is most effectively used for reading lines of input from a file, see "Looping Commands".

Table 8.11. The read Command
Format Meaning
read answer Reads a line from standard input and assigns it to the variable answer.
read first last Reads a line from standard input to the first whitespace or newline, putting the first word typed into the variable first and the rest of the line into the variable last.
Example 8.46
(The Script) $ cat nosy     #!/bin/sh     # Scriptname: nosy     echo "Are you happy? \c" 1   read answer     echo "$answer is the right response."     echo "What is your full name? \c" 2   read first middle last     echo "Hello  $first" ------------------------------------------------------------ (The Output)     $ nosy     Are you happy? Yes 1   Yes is the right response. 2   What is your full name? Jon Jake Jones     Hello Jon 

EXPLANATION

  1. The read command accepts a line of user input and assigns the input to the variable answer.

  2. The read command accepts input from the user and assigns the first word of input to the variable first, the second word of input to the variable middle, and all the rest of the words up to the end of the line to the variable last.

Example 8.47
(The Script) $ cat printer_check     #!/bin/sh     # Scriptname: printer_check     # Script to clear a hung up printer for SVR4 1   if [ $LOGNAME != root ]     then        echo "Must have root privileges to run this program"        exit 1     fi 2   cat << EOF     Warning: All jobs in the printer queue will be removed.     Please turn off the printer now. Press return when you     are ready to continue. Otherwise press Control C.     EOF 3   read ANYTHING      # Wait until the user turns off the printer     echo 4   /etc/init.d/lp stop       # Stop the printer 5   rm -f /var/spool/lp/SCHEDLOCK /var/spool/lp/temp*     echo     echo "Please turn the printer on now." 6   echo "Press return to continue" 7   read ANYTHING            # Stall until the user turns the printer                              # back on     echo                     # A blank line is printed 8   /etc/init.d/lp start     # Start the printer 

EXPLANATION

  1. Checks to see if user is root. If not, sends an error and exits.

  2. Creates a here document. Warning message is displayed on the screen.

  3. The read command waits for user input. When the user presses Enter, the variable ANYTHING accepts whatever is typed. The variable is not used for anything. The read in this case is used to wait until the user turns off the printer, comes back, and presses Enter.

  4. The lp program stops the printer daemon.

  5. The SCHEDLOCK file must be removed before the scheduler can start again, as well as temporary files in /var/spool/lp.

  6. The user is asked to press Return when ready.

  7. Whatever the user types is read into the variable ANYTHING, and when Enter is pressed, the program will resume execution.

  8. The lp program starts the print daemons.

8.2.3 Arithmetic

Arithmetic is not built into the Bourne shell. If you need to perform simple integer arithmetic calculations, the UNIX expr command is most commonly used in Bourne shell scripts. For floating point arithmetic, the awk or bc programs can be used. Because arithmetic was not built in, the performance of the shell is degraded when iterating through loops a number of times. Each time a counter is incremented or decremented in a looping mechanism, it is necessary to fork another process to handle the arithmetic.

Integer Arithmetic and the expr Command. The expr command is an expression-handling program. When used to evaluate arithmetic expressions, it can perform simple integer operations (see Table 8.12). Each of its arguments must be separated by a space. The +, -, *, /, and % operators are supported, and the normal programming rules of associativity and precedence apply.

Table 8.12. The expr Command Arithmetic Operators
Operator Function
* / % Multiplication, division, modulus.
+ - Addition, substraction.
Example 8.48
1   $ expr 1 + 4     5 2   $ expr 1+4     1+4 3   $ expr 5 + 9  /  3     8 4   $ expr 5 * 4     expr: syntax error 5   $ expr 5 \* 4  -  2     18 6   $ expr 11 % 3     2 7   $ num=1     $ num='expr $num + 1'     $ echo $num     2 

EXPLANATION

  1. The expr command evaluates the expression. The two numbers are added.

  2. Since there are no spaces between the operator, the expression is evaluated as a string.

  3. Addition and division are combined. The division is performed first and then the addition.

  4. The asterisk (*) is evaluated by the shell as one of its wildcards, causing the expr command to fail.

  5. The asterisk (*) is escaped with a backslash to prevent shell interpretation. The expr command performs arithmetic.

  6. The modulus operator (%) returns the remainder after division is performed.

  7. The variable num is assigned 1. The expr command adds one to the value of the variable and assigns the result to num. The value of num is echoed to the screen.

Floating Point Arithmetic. The bc, awk, and nawk utilities are useful if you need to perform more complex calculations.

Example 8.49
(The Command Line) 1   $ n='echo "scale=3; 13 / 2" | bc'     $ echo $n     6.500 2   product='nawk -v x=2.45 -v y=3.123 'BEGIN{printf "%.2f\n",x*y}''     $ echo $product     7.65 

EXPLANATION

  1. The output of the echo command is piped to the bc program. The scale is set to 3, which is the number of significant digits to the right of the decimal point that will be printed. The calculation is to divide 13 by 2. The entire pipeline is enclosed in backquotes. Command substitution will be performed and the output assigned to the variable n.

  2. The nawk program gets its values from the argument list passed in at the command line, x=2.45 y=3.123. (The v switch works with nawk, not awk.) After the numbers are multiplied, the printf function formats and prints the result with a precision of two places to the right of the decimal point. The output is assigned to the variable product.

8.2.4 Positional Parameters and Command Line Arguments

Information can be passed into a script via the command line. Each word (separated by whitespace) following the script name is called an argument.

Command line arguments can be referenced in scripts with positional parameters; for example, $1 for the first argument, $2 for the second argument, $3 for the third argument, and so on. The $# variable is used to test for the number of parameters, and $* is used to display all of them. Positional parameters can be set or reset with the set command. When the set command is used, any positional parameters previously set are cleared out. See Table 8.13.

Table 8.13. Positional Parameters
Positional Parameter What It References
$0 References the name of the script.
$# Holds the value of the number of positional parameters.
$* Lists all of the positional parameters.
$@ Means the same as $*, except when enclosed in double quotes.
"$*" Expands to a single argument (e.g., "$1 $2 $3").
"$@" Expands to separate arguments (e.g., "$1" "$2" 3").
$1 $9 References up to nine positional parameters.
Example 8.50
(The Script)     #!/bin/sh     # Scriptname: greetings     echo "This script is called $0." 1   echo "$0  $1 and $2"     echo "The number of positional parameters is $#"     ----------------------------------------------------------- (The Command Line)     $ chmod +x greetings 2   $ greetings     This script is called  greetings.     greetings  and     The number of positional paramters is 3   $ greetings Tommy     This script is called greetings.     greetings Tommy and     The number of positional parameters is 1 4   $ greetings Tommy  Kimberly     This script is called greetings.     greetings Tommy and Kimberly     The number of positional parameters is 2 

EXPLANATION

  1. In the script greetings, positional parameter $0 references the script name, $1 the first command line agreement, and $2 the second command line agreement.

  2. The greetings script is executed without any arguments passed. The output illustrates that the script is called greetings ($0 in the script) and that $1 and $2 were never assigned anything; therefore, their values are null and nothing is printed.

  3. This time, one argument is passed, Tommy. Tommy is assigned to positional parameter 1.

  4. Two arguments are entered, Tommy and Kimberly. Tommy is assigned to $1 and Kimberly is assigned to $2.

The set Command and Positional Parameters. The set command with arguments resets the positional parameters.[4] Once reset, the old parameter list is lost. To unset all of the positional parameters, use set . $0 is always the name of the script.

Example 8.51
(The Script)     $ cat args     #!/bin/sh     # Scriptname: args     # Script to test command line arguments 1   echo The name of this script is $0. 2   echo The arguments are $*. 3   echo The first argument is $1. 4   echo The second argument is $2. 5   echo The number of arguments is $#. 6   oldargs=$*             # Save parameters passed in from the                            # command line 7   set Jake Nicky Scott   # Reset the positional parameters 8   echo All the positional parameters are $*. 9   echo The number of postional parameters is $#. 10  echo "Good bye for now, $1 " 11  set 'date'          #  Reset the positional parameters 12  echo The date is $2 $3, $6. 13  echo "The value of \$oldargs is $oldargs." 14  set $oldargs 15  echo $1 $2 $3 (The Output)      $ args a b c d 1   The name of this script is args. 2   The arguments are a b c d. 3   The first argument is a. 4   The second argument is b. 5   The number of arguments is 4. 8   All the positional parameters are Jake Nicky Scott. 9   The number of positional parameters is 3. 10  Good-bye for now, Jake 12  The date is Mar 25, 2001. 13  The value of $oldargs is a b c d. 

EXPLANATION

  1. The name of the script is stored in the $0 variable.

  2. $* represents all of the positional parameters.

  3. $1 represents the first positional parameter (command line argument).

  4. $2 represents the second positional parameter.

  5. $# is the total number of positional parameters (command line arguments).

  6. All positional parameters are saved in a variable called oldargs.

  7. The set command allows you to reset the positional parameters, clearing out the old list. Now, $1 is Jake, $2 is Nicky, and $3 is Scott.

  8. $* represents all of the parameters, Jake, Nicky, and Scott.

  9. $# represents the number of parameters, 3.

  10. $1 is Jake.

  11. After command substitution is performed, i.e., date is executed, the positional parameters are reset to the output of the date command.

  12. The new values of $2, $3, and $6 are displayed.

  13. The values saved in oldargs are printed.

  14. The set command creates positional parameters from the value stored in oldargs.

  15. The first three positional parameters are displayed.

Example 8.52
(The Script)     $ cat checker     #!/bin/sh     # Scriptname: checker     # Script to demonstrate the use of special variable     # modifiers and arguments 1   name=${1:?"requires an argument" }     echo Hello $name (The Command Line) 2   $ checker     ./checker: 1: requires an argument 3   $ checker Sue     Hello Sue 

EXPLANATION

  1. The special variable modifier :? will check whether $1 has a value. If not, the script exits and the message is printed.

  2. The program is executed without an argument. $1 is not assigned a value; an error is displayed.

  3. The checker program is given a command line argument, Sue. In the script, $1 is assigned Sue. The program continues.

How $* and $@ Differ. The $* and $@ differ only when enclosed in double quotes. When $* is enclosed within double quotes, the parameter list becomes a single string. When $@ is enclosed within double quotes, each of the parameters is quoted; that is, each word is treated as a separate string.

Example 8.53
1   $ set 'apple pie' pears peaches 2   $ for i in $*     > do     > echo $i     > done     apple     pie     pears     peaches 3   $ set 'apple pie' pears peaches 4   $ for i in "$*"     > do     > echo $i     > done     apple pie pears peaches 5   $ set 'apple pie' pears peaches 6   $ for i in $@     > do     > echo $i     > done     apple     pie     pears     peaches 7   $ set 'apple pie' pears peaches 8   $ for i in "$@"          # At last!!     > do     > echo $i     > done     apple pie     pears     peaches 

EXPLANATION

  1. The positional parameters are set.

  2. When $* is expanded, the quotes surrounding apple pie are stripped; apple and pie become two separate words. The for loop assigns each of the words, in turn, to the variable i, and then prints the value of i. Each time through the loop, the word on the left is shifted off, and the next word is assigned to the variable i.

  3. The positional parameters are set.

  4. By enclosing $* in double quotes, the entire parameter list becomes one string, "apple pie pears peaches". The entire list is assigned to i as a single word. The loop makes one iteration.

  5. The positional parameters are set.

  6. Unquoted, $@ and $* behave the same way (see line 2 of this explanation).

  7. The positional parameters are set.

  8. By surrounding $@ with double quotes, each of the positional parameters is treated as a quoted string. The list would be "apple pie," "pears," "peaches." The desired result is finally achieved.

8.2.5 Conditional Constructs and Flow Control

Conditional commands allow you to perform some task(s) based on whether a condition succeeds or fails. The if command is the simplest form of decision-making; the if/else commands allow a two-way decision; and the if/elif/else commands allow a multiway decision.

The Bourne shell expects a command to follow an if. The command can be a system command or a built-in command. The exit status of the command is used to evaluate the condition.

To evaluate an expression, the built-in test command is used. This command is also linked to the bracket symbol. Either the test command is used, or the expression can be enclosed in set of single brackets. Shell metacharacters (wildcards) are not expanded by the test command. The result of a command is tested, with zero status indicating success and nonzero status indicating failure. See Table 8.14.

Testing Exit Status. The following examples illustrate how the exit status is tested.

Example 8.54
(At the Command Line) 1   $ name=Tom 2   $ grep "$name"  /etc/passwd     Tom:8ZKX2F:5102:40:Tom Savage:/home/tom:/bin/ksh 3   $ echo $?     0             Success! 4   $ test $name != Tom 5   $ echo $?     1             Failure 6   $ [ $name = Tom ]       # Brackets replace the test command 7   $ echo $?     0             Success 8   $ [ $name =  [Tt]?m ]   # Wildcards are not evaluated 9   $ echo  $?              # by the test command     1 

EXPLANATION

  1. The variable name is assigned the string Tom.

  2. The grep command will search for string Tom in the passwd file.

  3. The ? variable contains the exit status of the last command executed, in this case, the exit status of grep. If grep is successful in finding the string Tom, it will return an exit status of zero. The grep command was successful.

  4. The test command is used to evaluate strings, numbers, and perform file testing. Like all commands, it returns an exit status. If the exit status is zero, the expression is true; if the exit status is one, the expression evaluates to false. There must be spaces surrounding the equal sign. The value of name is tested to see if it is not equal to Tom.

  5. The test fails and returns an exit status of one.

  6. The brackets are an alternate notation for the test command. There must be spaces after the first bracket. The expression is tested to see if $name evaluates to the string Tom.

  7. The exit status of the test is zero. The test was successful because $name is equal to Tom.

  8. The test command does not allow wildcard expansion. Since the question mark is treated as a literal character, the test fails. Tom and [Tt]?m are not equal.

  9. The exit status is one indicating that the text in line 8 failed.

The test Command. The test command is used to evaluate conditional expressions, returning true or false. It will return a zero exit status for true and a nonzero exit status for false. The test command or brackets can be used.

Table 8.14. String, Integer, and File Testing
Test Operator Test For
String Test:
string1 = string2 String1 is equal to String2 (space surrounding = required).
string1 != string2 String1 is not equal to String2 (space surrounding != required).
string String is not null.
z string Length of string is zero.
n string Length of string is nonzero.
Example: test n $word or [ n $word ].
test tom = sue or [ tom = sue ].
Integer Test:
int1 eq int2 Int1 is equal to int2.
int1 ne int2 Int1 is not equal to int2.
int1 gt int2 Int1 is greater than int2.
int1 ge int2 Int1 is greater than or equal to int2.
int1 lt int2 Int1 is less than int2.
int1 le int2 Int1 is less than or equal to int2.
File Test:
b filename Block special file.
c filename Character special file.
d filename Directory existence.
f filename Regular file existence and not a directory.
g filename Set-group-ID is set.
k filename Sticky bit is set.
p filename File is a named pipe.
r filename File is readable.
s filename File is nonzero size.
u filename Set-user-ID bit is set.
w filename File is writeable.
x filename File is executable.

The if Command. The simplest form of conditional is the if command. The command or UNIX utility following the if construct is executed and its exit status is returned. The exit status is usually determined by the programmer who wrote the utility. If the exit status is zero, the command succeeded and the statement(s) after the then keyword are executed. In the C shell, the expression following the if command is a Boolean-type expression as in C. But in the Bourne and Korn shells, the statement following the if is a command or group of commands. If the exit status of the command being evaluated is zero, the block of statements after the then is executed until fi is reached. The fi terminates the if block. If the exit status is nonzero, meaning that the command failed in some way, the statement(s) after the then keyword are ignored and control goes to the line directly after the fi statement. It is important that you know the exit status of the commands being tested. For example, the exit status of grep is reliable in letting you know whether grep found the pattern it was searching for in a file. If grep is successful in its search, it returns a zero exit status; if not, it returns one. The sed and awk programs also search for patterns, but they will report a successful exit status regardless of whether they find the pattern. The criteria for success with sed and awk is correct syntax, not functionality.[5]

FORMAT

if commandthen then     command     command fi --------------------------------- if test expression then     command fi         or if [ expression ] then     command fi ------------------------------- 
Example 8.55
1   if ypmatch "$name" passwd > /dev/null 2>&1 2   then          echo Found $name! 3   fi 

EXPLANATION

  1. The ypmatch command is an NIS (Sun's Network Information Services) command that searches for its argument, name, in the NIS passwd database on the server machine. Standard output and standard error are redirected to /dev/null, the UNIX bit bucket. If ypmatch is not supported on your system, try

    if grep "$name" /etc/passwd > /dev/null 2>&1 
  2. If the exit status of the ypmatch command is zero, the program goes to the then statement and executes commands until fi is reached.

  3. The fi terminates the list of commands following the then statement.

Example 8.56
1   echo  "Are you okay (y/n) ?"     read answer 2   if [ "$answer" = Y -o "$answer" = y  ]     then        echo  "Glad to hear it." 3   fi 

EXPLANATION

  1. The user is asked the question and told to respond. The read command waits for a response.

  2. The test command, represented by square brackets, is used to test expressions. It returns an exit status of zero if the expression is true and nonzero if the expression is false. If the variable answer evaluates to Y or y, the commands after the then statement are executed. (The test command does not allow the use of wildcards when testing expressions, and spaces must surround the square brackets. (and the = operators.) See Table 8.14.

    $answer is double quoted to hold it together as a single string. The test command fails if more than one word appears before the = operator. For example, if the user entered yes, you betcha, the answer variable would evaluate to three words, causing the test to fail, unless $answer is enclosed in double quotes.

  3. The fi terminates the list of commands following the then statement.

The exit Command and the ? Variable. The exit command is used to terminate the script and return to the command line. You may want the script to exit if some condition occurs. The argument given to the exit command is a number ranging from 0 to 255. If the program exits with zero as an argument, the program exited with success. A nonzero argument indicates some kind of failure. The argument given to the exit command is stored in the shell's ? variable.

Example 8.57
(The Script) $ cat bigfiles     # Name: bigfiles     # Purpose: Use the find command to find any files in the root     # partition that have not been modified within the past n (any     # number within 30 days) days and are larger than 20 blocks     # (512-byte blocks) 1   if  [ $# -ne 2 ]     then         echo  "Usage: $0 mdays size " 1>&2         exit 1 2   fi 3   if  [ $1 -lt 0 -o $1 -gt 30 ]     then         echo "mdays is out of range"         exit 2 4   fi 5   if [ $2 -le 20 ]     then         echo "size is out of range"         exit 3 6   fi 7   find / -xdev -mtime $1 -size +$2 -print (The Command Line) $ bigfiles Usage: bigfiles mdays size $ echo $? 1 $ bigfiles 400 80 mdays is out of range $ echo $? 2 $ bigfiles 25 2 size is out of range $ echo $? 3 $ bigfiles 2 25 (Output of find prints here) 

EXPLANATION

  1. The statement reads: If the number of arguments is not equal to 2, print the error message and send it to standard error, then exit the script with an exit status of 1.

  2. The fi marks the end of the block of statements after then.

  3. The statement reads: If the value of the first positional parameter passed in from the command line is less than 0 or greater than 30, then print the message and exit with a status of 2. See Table 8.14 for numeric operators.

  4. The fi ends the if block.

  5. The statement reads: If the value of the second positional parameter passed in at the command line is less than or equal to 20 (512-byte blocks), then print the message and exit with a status of 3.

  6. The fi ends the if block.

  7. The find command starts its search in the root directory.The x dev option prevents find from searching other partitions. The mtime option takes a number argument, which is the number of days since the file was modified, and the size option takes a number argument, which is the size of the file in 512-byte blocks.

Checking for Null Values. When checking for null values in a variable, use double quotes to hold the null value or the test command will fail.

Example 8.58
(The Script) 1   if [ "$name" = "" ]         # Alternative to  [ ! "$name" ]  or  [ -z "$name" ]     then        echo The name variable is null     fi (From System showmount program, which displays all remotely mounted systems)     remotes='/usr/sbin/showmount' 2   if [ "X${remotes}" != "X" ]     then        /usr/sbin/wall ${remotes}                     ... 3   fi 

EXPLANATION

  1. If the name variable evaluates to null, the test is true. The double quotes are used to represent null.

  2. The showmount command lists all clients remotely mounted from a host machine. The command will list either one or more clients, or nothing. The variable remotes will either have a value assigned or will be null. The letter X precedes the variable remotes when being tested. If remotes evaluates to null, no clients are remotely logged on and X will be equal to X, causing the program to start execution again on line 3. If the variable has a value, for example, the hostname pluto, the expression would read if Xpluto != X, and the wall command would be executed. (All users on remote machines will be sent a message.) The purpose of using X in the expression is to guarantee that even if the value of remotes is null, there will always be a placeholder on either side of the != operator in the expression.

  3. The fi terminates the if.

The if/else Command. The if/else commands allow a two-way decision-making process. If the command after the if fails, the commands after the else are executed.

FORMAT

if  command then     command(s) else     command(s) fi 
Example 8.59
(The Script)     #!/bin/sh 1   if ypmatch "$name" passwd > /dev/null 2>&1[6] 2   then         echo Found $name! 3   else 4       echo  "Can't find $name."         exit 1 5   fi 

EXPLANATION

  1. The ypmatch command searches for its argument, name, in the NIS passwd database. Since the user does not need to see the output, standard output and standard error are redirected to /dev/null, the UNIX bit bucket.

  2. If the exit status of the ypmatch command is zero, program control goes to the then statement and executes commands until else is reached.

  3. The commands under the else statement are executed if the ypmatch command fails to find $name in the passwd database; that is, the exit status of ypmatch must be nonzero for the commands in the else block to be executed.

  4. If the value in $name is not found in the passwd database, this echo statement is executed and the program exits with a value of one, indicating failure.

  5. The fi terminates the if.

Example 8.60
(The Script) $ cat idcheck #!/bin/sh # Scriptname: idcheck # purpose:check user ID to see if user is root. # Only root has a uid of 0. # Format for id output:uid=9496(ellie) gid=40 groups=40 # root's uid=0 1   id='id | nawk  F'[=(]'  '{print $2}''     # Get user ID     echo your user id is: $id 2   if [ $id  eq 0 ]     then 3       echo "you are superuser." 4   else         echo "you are not superuser." 5   fi     (The Command Line) 6   $ idcheck     your user id is: 9496     you are not superuser. 7   $ su     Password: 8   # idcheck     your user id is: 0     you are superuser 

EXPLANATION

  1. The id command is piped to the nawk command. Nawk uses an equal sign and open parenthesis as field separators, extracts the user ID from the output, and assigns the output to the variable id.

  2. If the value of id is equal to zero, then line 3 is executed

  3. If ID is not equal to zero, the else statements are executed.

  4. The fi marks the end of the if command.

  5. The idcheck script is executed by the current user, whose UID is 9496.

  6. The su command switches the user to root.

  7. The # prompt indicates that the superuser (root) is the new user. The UID for root is 0.

The if/elif/else Command. The if/elif/else commands allow a multiway decision- making process. If the command following the if fails, the command following the elif is tested. If that command succeeds, the commands under its then statement are executed. If the command after the elif fails, the next elif command is checked. If none of the commands succeeds, the else commands are executed. The else block is called the default.

FORMAT

if  command then     command(s) elif command then     commands(s) elif command then     command(s) else     command(s) fi 
Example 8.61
(The Script) $ cat tellme     #!/bin/sh     # Scriptname: tellme 1   echo -n "How old are you? "     read age 2   if  [  $age -lt  0 -o $age -gt 120 ]     then         echo  "Welcome to our planet! "         exit 1     fi 3   if [ $age -ge 0 -a  $age -lt 13 ]     then         echo "A child is a garden of verses"     elif [ $age -ge 12 -a $age -lt  20 ]     then         echo "Rebel without a cause"     elif [ $age  -gt 20 -a  $age -lt  30 ]     then         echo "You got the world by the tail!!"     elif [ $age -ge  30 -a  $age -lt 40 ]     then         echo "Thirty something..." 4   else         echo "Sorry I asked" 5   fi (The Output) $ tellme How old are you? 200 Welcome to our planet! $ tellme How old are you? 13 Rebel without a cause $ tellme How old are you? 55 Sorry I asked 

EXPLANATION

  1. The user is asked for input. The input is assigned to the variable age.

  2. A numeric test is performed within the square brackets. If age is less than 0 or greater than 120, the echo command is executed and the program terminates with an exit status of one. The interactive shell prompt will appear.

  3. A numeric test is performed within the square brackets. If age is greater than 0 and less than 13, the test command returns exit status zero, true, and the statement after the then is executed. Otherwise, program control goes to the elif. If that test is false, the next elif is tested.

  4. The else construct is the default. If none of the above statements are true, the else commands will be executed.

  5. The fi terminates the initial if statement.

File Testing. Often when writing scripts, your script will require that there are certain files available and that those files have specific permissions, are of a certain type, or have other attributes. (See Table 8.14.) You will find file testing a necessary part of writing dependable scripts.

When if statements are nested, the fi statement always goes with the nearest if statement. Indenting the nested ifs makes it easier to see which if statement goes with which fi statement.

Example 8.62

graphics/08prfig02.gif

EXPLANATION

  1. If the file testing is a directory, print testing is a directory.

  2. If the file testing is not a directory, else if the file is a plain file, then

  3. If the file testing is readable, writeable, and executable, then

  4. The fi terminates the innermost if command.

  5. The else commands are executed if lines 1 and 2 are not true.

  6. This fi goes with the first if.

The null Command. The null command, represented by a colon, is a built-in, do-nothing command that returns an exit status of zero. It is used as a placeholder after an if command when you have nothing to say, but need a command or the program will produce an error message because it requires something after the then statement. Often the null command is used as an argument to the loop command to make the loop a forever loop.

Example 8.63
(The Script) 1   name=Tom 2   if grep "$name" databasefile > /dev/null 2>&1     then 3       : 4   else         echo  "$1 not found in databasefile"         exit 1     fi 

EXPLANATION

  1. The variable name is assigned the string Tom.

  2. The if command tests the exit status of the grep command. If Tom is found in the database file, the null command, a colon, is executed and does nothing.

  3. The colon is the null command. It does nothing other than returning a 0 exit status.

  4. What we really want to do is print an error message and exit if Tom is not found. The commands after the else will be executed if the grep command fails.

Example 8.64
(The Command Line) 1   $ DATAFILE= 2   $ : ${DATAFILE:=$HOME/db/datafile}     $ echo $DATAFILE     /home/jody/ellie/db/datafile 3   $ : ${DATAFILE:=$HOME/junk}     $ echo $DATAFILE     /home/jody/ellie/db/datafile 

EXPLANATION

  1. The variable DATAFILE is assigned null.

  2. The colon command is a "do-nothing" command. The modifier (:=) returns a value that can be assigned to a variable or used in a test. In this example, the expression is passed as an argument to the do-nothing command. The shell will perform variable substitution; that is, assign the pathname to DATAFILE if DATAFILE does not already have a value. The variable DATAFILE is permanently set.

  3. Since the variable has already been set, it will not be reset with the default value provided on the right of the modifier.

Example 8.65
(The Script) $ cat wholenum     #!/bin/sh 1   # Name:wholenum     # Purpose:The expr command tests that the user enters an integer     echo "Enter a number."     read number 2   if expr "$number" + 0 > /dev/null 2>&1     then 3      :     else 4       echo "You did not enter an integer value."         exit 1 5   fi 

EXPLANATION

  1. The user is asked to enter an integer. The number is assigned to the variable number.

  2. The expr command evaluates the expression. If addition can be performed, the number is a whole number and expr returns a successful exit status. All output is redirected to the bit bucket /dev/null.

  3. If expr is successful, it returns a zero exit status, and the colon command does nothing.

  4. If the expr command fails, it returns a nonzero exit status, the echo command displays the message, and the program exits.

  5. The fi ends the if block.

8.2.6 The case Command

The case command is a multiway branching command used as an alternative to if/elif commands. The value of the case variable is matched against value1, value2, and so forth, until a match is found. When a value matches the case variable, the commands following the value are executed until the double semicolons are reached. Then execution starts after the word esac (case spelled backwards).

If the case variable is not matched, the program executes the commands after the *), the default value, until ;; or esac is reached. The *) value functions the same as the else statement in if/else conditionals. The case values allow the use of shell wildcards and the vertical bar (pipe symbol) for oring two values.

FORMAT

case variable in value1)     command(s)     ;; value2)     command(s)     ;; *) command(s)     ;; esac 
Example 8.66
(The Script) $ cat colors     #!/bin/sh     # Scriptname: colors 1   echo -n "Which color do you like?"     read color 2   case "$color" in 3   [Bb]l??) 4       echo I feel $color         echo The sky is $color 5       ;; 6   [Gg]ree*)         echo  $color is for trees         echo  $color is for seasick;; 7   red | orange)             # The vertical bar means "or"         echo  $color is very warm!;; 8   *)         echo  No such color as $color;; 9   esac 10  echo  "Out of case" 

EXPLANATION

  1. The user is asked for input. The input is assigned to the variable color.

  2. The case command evaluates the expression $color.

  3. If the color begins with a B or b, followed by the letter l and any two characters, the case expression matches the first value. The value is terminated with a single closed parenthesis. The wildcards are shell metacharacters used for filename expansion.

  4. The statements are executed if the value in line number 3 matches the case expression.

  5. The double semicolons are required after the last command in this block of commands. Control branches to line 10 when the semicolons are reached. The script is easier to debug if the semicolons are on their own line.

  6. If the case expression matches a G or g, followed by the letters ree and ending in zero or more of any other characters, the echo commands are executed. The double semicolons terminate the block of statements and control branches to line 10.

  7. The vertical bar is used as an or conditional operator. If the case expression matches either red or orange, the echo command is executed.

  8. This is the default value. If none of the above values match the case expression, the commands after the *) are executed.

  9. The esac statement terminates the case command.

  10. After one of the case values are matched, execution continues here.

Creating Menus with the here document and case Command. The here document and case command are often used together. The here document is used to create a menu of choices that will be displayed to the screen. The user will be asked to select one of the menu items, and the case command will test against the set of choices to execute the appropriate command.

Example 8.67
(The .profile File)     echo "Select a terminal type: " 1   cat << ENDIT         1) vt 120         2) wyse50         3) sun 2   ENDIT 3   read choice 4   case "$choice" in 5   1)  TERM=vt120         export TERM         ;;     2)  TERM=wyse50         export TERM         ;; 6   3)  TERM=sun         export TERM         ;; 7   esac 8   echo "TERM is $TERM." (The Output) $ . .profile Select a terminal type: 1) vt120 2) wyse50 3) sun 3               <-- User input TERM is sun. 

EXPLANATION

  1. If this segment of script is put in the .profile, when you log on, you will be given a chance to select the proper terminal type. The here document is used to display a menu of choices.

  2. The user-defined ENDIT terminator marks the end of the here document.

  3. The read command stores the user input in the variable TERM.

  4. The case command evaluates the variable TERM and compares that value with one of the values preceding the closing parenthesis: 1, 2, or *.

  5. The first value tested is 1. If there is a match, the terminal is set to a vt120. The TERM variable is exported so that subshells will inherit it.

  6. A default value is not required. The TERM variable is normally assigned in /etc/profile at login time. If the choice is 3, the terminal is set to a sun.

  7. The esac terminates the case command.

  8. After the case command has finished, this line is executed.

8.2.7 Looping Commands

Looping commands are used to execute a command or group of commands a set number of times or until a certain condition is met. The Bourne shell has three types of loops: the for loop, the while loop, and the until loop.

The for Command. The for looping command is used to execute commands a finite number of times on a list of items. For example, you might use this loop to execute the same commands on a list of files or usernames. The for command is followed by a user-defined variable, the keyword in, and a list of words. The first time in the loop, the first word from the wordlist is assigned to the variable, and then shifted off the list. Once the word is assigned to the variable, the body of the loop is entered, and commands between the do and done keywords are executed. The next time around the loop, the second word is assigned to the variable, and so on. The body of the loop starts at the do keyword and ends at the done keyword. When all of the words in the list have been shifted off, the loop ends and program control continues after the done keyword.

FORMAT

for variable in word_list do     command(s) done 
Example 8.68
(The Script) $ cat forloop     #!/bin/sh     # Scriptname: forloop 1   for pal in Tom Dick Harry Joe 2   do 3       echo "Hi $pal" 4   done 5   echo "Out of loop" (The Output) $ forloop Hi Tom Hi Dick Hi Harry Hi Joe Out of loop 

EXPLANATION

  1. This for loop will iterate through the list of names, Tom, Dick, Harry, and Joe, shifting each one off (to the left and assigning its value to the user-defined variable, pal) after it is used. As soon as all of the words are shifted and the wordlist is empty, the loop ends and execution starts after the done keyword. The first time in the loop, the variable pal will be assigned the word Tom. The second time through the loop, pal will be assigned Dick, the next time pal will be assigned Harry, and the last time pal will be assigned Joe.

  2. The do keyword is required after the wordlist. If it is used on the same line, the list must be terminated with a semicolon. Example:

    for pal in Tom Dick Harry Joe; do 
  3. This is the body of the loop. After Tom is assigned to the variable pal, the commands in the body of the loop (i.e., all commands between the do and done keywords) are executed.

  4. The done keyword ends the loop. Once the last word in the list (Joe) has been assigned and shifted off, the loop exits.

  5. Control resumes here when the loop exits.

Example 8.69
(The Command Line) 1   $ cat mylist    tom    patty    ann    jake (The Script) $ cat mailer     #!/bin/sh     # Scriptname: mailer 2   for person in 'cat mylist'     do 3       mail $person < letter         echo  $person was sent a letter. 4   done 5   echo "The letter has been sent." 

EXPLANATION

  1. The contents of a file, called mylist, are displayed.

  2. Command substitution is performed and the contents of mylist becomes the wordlist. The first time in the loop, tom is assigned to the variable person, then it is shifted off to be replaced with patty, and so forth.

  3. In the body of the loop, each user is mailed a copy of a file called letter.

  4. The done keyword marks the end of this loop iteration.

  5. When all of the users in the list have been sent mail and the loop has exited, this line is executed.

Example 8.70
(The Script)     #!/bin/sh     # Scriptname: backup     # Purpose:     # Create backup files and store them in a backup directory 1   dir=/home/jody/ellie/backupscripts 2   for file in memo[1-5]     do         if [ -f $file ]         then              cp $file $dir/$file.bak              echo "$file is backed up in $dir"         fi     done (The Output) memo1 is backed up in /home/jody/ellie/backupscripts memo2 is backed up in /home/jody/ellie/backupscripts memo3 is backed up in /home/jody/ellie/backupscripts memo4 is backed up in /home/jody/ellie/backupscripts memo5 is backed up in /home/jody/ellie/backupscripts 

EXPLANATION

  1. The variable dir is assigned.

  2. The wordlist will consist of all files in the current working directory with names starting with memo and ending with a number between 1 and 5. Each filename will be assigned, one at time, to the variable file for each iteration of the loop.

  3. When the body of the loop is entered, the file will be tested to make sure it exists and is a real file. If so, it will be copied into the directory /home/jody/ellie/backupscripts with the .bak extension appended to its name.

The $* and $@ Variables in Wordlists. When expanded, the $* and $@ are the same unless enclosed in double quotes. $* evaluates to one string, whereas $@ evaluates to a list of separate words.

Example 8.71
(The Script) $ cat greet     #!/bin/sh     # Scriptname: greet 1   for name in $*      # Same as for name in $@ 2   do        echo Hi $name 3   done (The Command Line) $ greet Dee Bert Lizzy Tommy Hi Dee Hi Bert Hi Lizzy Hi Tommy 

EXPLANATION

  1. $* and $@ expand to a list of all the positional parameters, in this case, the arguments passed in from the command line: Dee, Bert, Lizzy, and Tommy. Each name in the list will be assigned, in turn, to the variable name in the for loop.

  2. The commands in the body of the loop are executed until the list is empty.

  3. The done keyword marks the end of the loop body.

Example 8.72
(The Script) $ cat permx     #!/bin/sh     # Scriptname:permx 1   for file         # Empty wordlist     do 2      if [ -f $file -a ! -x $file ]        then 3               chmod +x $file                 echo $file now has execute permission        fi     done (The Command Line) 4   $ permx *     addon now has execute permission     checkon now has execute permission     doit now has execute permission 

EXPLANATION

  1. If the for loop is not provided with a wordlist, it iterates through the positional parameters. This is the same as for file in $*.

  2. The filenames are coming in from the command line. The shell expands the asterisk (*) to all filenames in the current working directory. If the file is a plain file and does not have execute permission, line 3 is executed.

  3. Execute permission is added for each file being processed.

  4. At the command line, the asterisk will be evaluated by the shell as a wildcard and all files in the current directory will be replaced for the *. The files will be passed as arguments to the permx script.

The while Command. The while evaluates the command immediately following it and, if its exit status is zero, the commands in the body of the loop (commands between do and done) are executed. When the done keyword is reached, control is returned to the top of the loop and the while command checks the exit status of the command again. Until the exit status of the command being evaluated by the while becomes nonzero, the loop continues. When the exit status reaches nonzero, program execution starts after the done keyword.

FORMAT

while command do     command(s) done 
Example 8.73
(The Script) $ cat num     #!/bin/sh     # Scriptname: num 1   num=0       # Initialize num 2   while  [ $num -lt 10 ]          # Test num with test command     do        echo  -n $num 3      num='expr  $num + 1          # Increment num     done     echo "\nAfter loop exits, continue running here" (The Output) 0123456789 After loop exits, continue running here 

EXPLANATION

  1. This is the initialization step. The variable num is assigned 0.

  2. The while command is followed by the test (square brackets) command. If the value of num is less than 10, the body of the loop is entered.

  3. In the body of the loop, the value of num is incremented by one. If the value of num never changes, the loop would iterate infinitely or until the process is killed.

Example 8.74
(The Script)     #!/bin/sh     # Scriptname: quiz 1   echo "Who was the chief defense lawyer in the OJ case?"     read answer 2   while [ "$answer" != "Johnny" ] 3   do          echo  "Wrong try again!" 4        read answer 5   done 6   echo You got it! (The Output) $ quiz Who was the chief defense lawyer in the OJ case? Marcia Wrong try again! Who was the chief defense lawyer in the OJ case?  I give up Wrong try again! Who was the chief defense lawyer in the OJ case?  Johnny You got it! 

EXPLANATION

  1. The echo command prompts the user, Who was the chief defense lawyer in the OJ case? The read command waits for input from the user. The input will be stored in the variable answer.

  2. The while loop is entered and the test command, the bracket, tests the expression. If the variable answer does not equal the string Johnny, the body of the loop is entered and commands between the do and done are executed.

  3. The do keyword is the start of the loop body.

  4. The user is asked to re-enter input.

  5. The done keyword marks the end of the loop body. Control is returned to the top of the while loop, and the expression is tested again. As long as $answer does not evaluate to Johnny, the loop will continue to iterate. When the user enters Johnny, the loop ends. Program control goes to line 6.

  6. When the body of the loop ends, control starts here.

Example 8.75
(The Script) $ cat sayit     #!/bin/sh     # Scriptname: sayit     echo Type q to quit.     go=start 1   while [ -n "$go" ]    # Make sure to double quote the variable     do 2       echo -n I love you. 3       read word 4       if [ "$word" = q -o "$word" = Q ]         then                 echo "I'll always love you!"                 go=         fi     done (The Output) $ sayit Type q to quit. I love you.     When user presses the Enter key, the program continues I love you. I love you. I love you. I love you.q I'll always love you! $ 

EXPLANATION

  1. The command after the while is executed and its exit status tested. The n option to the test command tests for a nonnull string. Since go initially has a value, the test is successful, producing a zero exit status. If the variable go is not enclosed in double quotes and the variable is null, the test command would complain:

    go: test: argument expected 
  2. The loop is entered. The string I love you. is echoed to the screen.

  3. The read command waits for user input.

  4. The expresson is tested. If the user enters a q or Q, the string I'll always love you! is displayed, and the variable go is set to null. When the while loop is re-entered, the test is unsuccessful since the variable is null. The loop terminates. Control goes to the line after the done statement. In this example, the script will terminate since there are no more lines to execute.

The until Command. The until command is used like the while command, but executes the loop statements only if the command after until fails, i.e., if the command returns an exit status of nonzero. When the done keyword is reached, control is returned to the top of the loop and the until command checks the exit status of the command again. Until the exit status of the command being evaluated by until becomes zero, the loop continues. When the exit status reaches zero, the loop exits, and program execution starts after the done keyword.

FORMAT

until command do     command(s) done 
Example 8.76
    #!/bin/sh 1   until who | grep linda 2   do         sleep 5 3   done     talk linda@dragonwings 

EXPLANATION

  1. The until loop tests the exit status of the last command in the pipeline, grep. The who command lists who is logged on this machine and pipes its output to grep. The grep command will return a zero exit status (success) only when it finds user linda.

  2. If user linda has not logged on, the body of the loop is entered and the program sleeps for five seconds.

  3. When linda logs on, the exit status of the grep command will be zero and control will go to the statements following the done keyword.

Example 8.77
(The Script) $ cat hour     #!/bin/sh     # Scriptname: hour 1   hour=1 2   until [ $hour  -gt 24 ]     do 3       case "$hour" in         [0-9] |1[0-1])  echo  "Good morning!"              ;;         12) echo  "Lunch time."             ;;         1[3-7]) echo  "Siesta time."             ;;         *) echo  "Good night."            ;;         esac 4       hour='expr $hour + 1' 5   done (The Output) $ hour Good morning! Good morning!     ... Lunch time. Siesta time.     ... Good night.     ... 

EXPLANATION

  1. The variable hour is initialized to 1.

  2. The test command tests if the hour is greater than 24. If the hour is not greater than 24, the body of the loop is entered. The until loop is entered if the command following it returns a nonzero exit status. Until the condition is true, the loop continues to iterate.

  3. The case command evaluates the hour variable and tests each of the case statements for a match.

  4. The hour variable is incremented before control returns to the top of the loop.

  5. The done command marks the end of the loop body.

Looping Commands. If some condition occurs, you may want to break out of a loop, return to the top of the loop, or provide a way to stop an infinite loop. The Bourne shell provides loop control commands to handle these kinds of situations.

The shift Command. The shift command shifts the parameter list to the left a specified number of times. The shift command without an argument shifts the parameter list once to the left. Once the list is shifted, the parameter is removed permanently. Often, the shift command is used in a while loop when iterating through a list of positional parameters.

FORMAT

shift [n] 
Example 8.78
(Without a Loop) (The Script) 1   set joe mary tom sam 2   shift 3   echo $* 4   set 'date' 5   echo  $* 6   shift 5 7   echo  $* 8   shift 2 (The Output) 3   mary tom sam 5   Fri Sep 9 10:00:12 PDT 2001 7   2001 8   cannot shift 

EXPLANATION

  1. The set command sets the positional parameters. $1 is assigned joe, $2 is assigned mary, $3 is assigned tom, and $4 is assigned sam. $* represents all of the parameters.

  2. The shift command shifts the positional parameters to the left; joe is shifted off.

  3. The parameter list is printed after the shift.

  4. The set command resets the positional parameters to the output of the UNIX date command.

  5. The new parameter list is printed.

  6. This time the list is shifted 5 times to the left.

  7. The new parameter list is printed.

  8. By attempting to shift more times than there are parameters, the shell sends a message to standard error.

Example 8.79
(With a Loop) (The Script) $ cat doit     #!/bin/sh     # Name: doit     # Purpose: shift through command line arguments     # Usage: doit [args] 1    while [  $# -gt  0 ]      do 2       echo  $* 3       shift 4    done (The Command Line) $ doit a b c d e a b c d e b c d e c d e d e e 

EXPLANATION

  1. The while command tests the numeric expression. If the number of positional parameters ($#) is greater than 0, the body of the loop is entered. The positional parameters are coming from the command line as arguments. There are five.

  2. All positional parameters are printed.

  3. The parameter list is shifted once to the left.

  4. The body of the loop ends here; control returns to the top of the loop. Each time the loop is entered, the shift command causes the parameter list to be decreased by one. After the first shift, $# (number of positional parameters) is four. When $# has been decreased to zero, the loop ends.

Example 8.80
(The Script) $ cat dater     #!/bin/sh     # Scriptname: dater     # Purpose: set positional parameters with the set command     # and shift through the parameters. 1   set 'date' 2   while [ $# -gt 0 ]     do 3      echo $1 4      shift     done (The Output) $ dater Sat Oct 13 12:12:13 PDT 2001 

EXPLANATION

  1. The set command takes the output of the date command and assigns the output to positional parameters $1 through $6.

  2. The while command tests whether the number of positional parameters ($#) is greater than 0. If true, the body of the loop is entered.

  3. The echo command displays the value of $1, the first positional parameter.

  4. The shift command shifts the parameter list once to the left. Each time through the loop, the list is shifted until the list is empty. At that time, $# will be zero and the loop terminates.

The break Command. The built-in break command is used to force immediate exit from a loop, but not from a program. (To leave a program, the exit command is used.) After the break command is executed, control starts after the done keyword. The break command causes an exit from the innermost loop, so if you have nested loops, the break command takes a number as an argument, allowing you to break out of a specific outer loop. If you are nested in three loops, the outermost loop is loop number 3, the next nested loop is loop number 2, and the innermost nested loop is loop number 1. The break is useful for exiting from an infinite loop.

FORMAT

break [n] 
Example 8.81

graphics/08prfig03.gif

EXPLANATION

  1. The true command is a UNIX command that always exits with zero status. It is often used to start an infinite loop. It is okay to put the do statement on the same line as the while command, as long as a semicolon separates them. The body of the loop is entered.

  2. The user is asked for input. The user's input is assigned to the variable answer.

  3. If $answer evaluates to Y or y, control goes to line 4.

  4. The break command is executed, the loop is exited, and control goes to line 7. The line Here we are is printed. Until the user answers with a Y or y, the program will continue to ask for input. This could go on forever!

  5. If the test fails in line 3, the else commands are executed. When the body of the loop ends at the done keyword, control starts again at the top of the while at line 1.

  6. This is the end of the loop body.

  7. Control starts here after the break command is executed.

The continue Command. The continue command returns control to the top of the loop if some condition becomes true. All commands below the continue will be ignored. If nested within a number of loops, the continue command returns control to the innermost loop. If a number is given as its argument, control can then be started at the top of any loop. If you are nested in three loops, the outermost loop is loop number 3, the next nested loop is loop number 2, and the innermost nested loop is loop number 1.[7]

FORMAT

continue [n] 
Example 8.82

graphics/08prfig04.gif

EXPLANATION

  1. After command substitution, cat mail_list, the for loop will iterate through the list of names from the file called mail_list.

  2. If the name matches richard, the continue command is executed and control goes back to top of the loop where the loop expression is evalutated. Since richard has already been shifted off the list, the next user, melanie, will be assigned to the variable name. The string richard does not need to be quoted here because it is only one word. But, it is good practice to quote the string after the test's = operator because if the string consisted of more than one word, for example, richard jones, the test command would produce an error message:

    test: unknown operator richard 
  3. The continue command returns control to the top of the loop, skipping any commands in the rest of the loop body.

  4. All users in the list, except richard, will be mailed a copy of the file memo.

  5. This is the end of the loop body.

Nested Loops and Loop Control. When using nested loops, the break and continue commands can be given a numeric, integer argument so that control can go from the inner loop to an outer loop.

Example 8.83

graphics/08prfig05.gif

EXPLANATION

  1. The outer for loop is started. The first time in the loop, Jan is assigned to month.

  2. The inner for loop starts. The first time in this loop, 1 is assigned to week. The inner loop iterates completely before going back to the outer loop.

  3. If the user enters either an n or presses Enter, line 4 is executed.

  4. The continue command with an argument of 2 starts control at the top of the second outermost loop. The continue without an argument returns control to the top of the innermost loop.

  5. Control is returned to the innermost for loop.

  6. This done terminates the innermost loop.

  7. This done terminates the outermost loop.

I/O Redirection and Subshells. Input can be piped or redirected to a loop from a file. Output can also be piped or redirected to a file from a loop. The shell starts a subshell to handle I/O redirection and pipes. Any variables defined within the loop will not be known to the rest of the script when the loop terminates.

Redirecting the Output of the Loop to a File

Example 8.84
(The Command Line) 1   $ cat memo     abc     def     ghi (The Script) $ cat numbers     #!/bin/sh     # Program name: numberit     # Put line numbers on all lines of memo 2   if [ $# -lt  1 ]     then 3       echo  "Usage: $0 filename " >&2         exit 1     fi 4   count=1                     # Initialize count 5   cat $1 | while read line     # Input is coming from file on command line     do 6       [ $count -eq 1 ] && echo "Processing file $1..." > /dev/tty 7       echo $count $line 8       count='expr $count + 1' 9   done > tmp$$                 # Output is going to a temporary file 10  mv tmp$$ $1 (The Command Line) 11  $ numberit memo     Processing file memo...  12 $ cat memo     1 abc     2 def     3 ghi 

EXPLANATION

  1. The contents of file memo are displayed.

  2. If the user did not provide a command line argument when running this script, the number of arguments ($#) will be less than one and the error message appears.

  3. The usage message is sent to stderr (>&2) if the number of arguments is less than 1.

  4. The count variable is assigned the value 1.

  5. The UNIX cat command displays the contents of the filename stored in $1, and the output is piped to the while loop. The read command is assigned the first line of the file the first time in the loop, the second line of the file the next time through the loop, and so forth. The read command returns a zero exit status if it is successful in reading input and one if it fails.

  6. If the value of count is 1, the echo command is executed and its output is sent to /dev/tty, the screen.

  7. The echo command prints the value of count, followed by the line in the file.

  8. The count is incremented by one.

  9. The output of this entire loop, each line of the file in $1, is redirected to the file tmp$$, with the exception of the first line of the file, which is redirected to the terminal, /dev/tty.[8]

  10. The tmp file is renamed to the filename assigned to $1.

  11. The program is executed. The file to be processed is called memo.

  12. The file memo is displayed after the script has finished, demonstrating that line numbers have been prepended to each line.

Example 8.85
(The File) $ cat testing apples pears peaches (The Script)     #!/bin/sh     # This program demonstrates the scope of variables when     # assigned within loops where the looping command uses     # redirection. A subshell is started when the loop uses     # redirection, making all variables created within the loop     # local to the shell where the loop is being executed. 1   while read line     do 2       echo $line    # This line will be redirected to outfile 3       name=JOE 4   done < testing > outfile  # Redirection of input and output 5   echo Hi there $name (The Output) 5   Hi there 

EXPLANATION

  1. If the exit status of the read command is successful, the body of the while loop is entered. The read command is getting input from the file testing, named after the done on line 4. Each time through the loop, the read command reads another line from the file testing.

  2. The value of line will be redirected to outfile in line 4.

  3. The variable name is assigned JOE. Since redirection is utilized in this loop, the variable is local to the loop.

  4. The done keyword consists of the redirection of input from the file testing, and the redirection of output to the file outfile. All output from this loop will go to outfile.

  5. When out of the loop, name is undefined. It was local to the while loop and known only within the body of that loop. Since the variable name has no value, only the string Hi there is displayed.

Piping the Output of a Loop to a UNIX Command. Output can be either piped to another command(s) or redirected to a file.

Example 8.86
(The Script)     #!/bin/sh 1   for i in 7 9 2 3 4  5 2   do         echo  $i 3   done | sort  n (The Output) 2 3 4 5 7 9 

EXPLANATION

  1. The for loop iterates through a list of unsorted numbers.

  2. In the body of the loop, the numbers are printed. This output will be piped into the UNIX sort command, a numerical sort.

  3. The pipe is created after the done keyword. The loop is run in a subshell.

Running Loops in the Background. Loops can be executed to run in the background. The program can continue without waiting for the loop to finish processing.

Example 8.87
(The Script)     #!/bin/sh 1   for person in bob jim joe sam     do 2       mail $person < memo 3   done & 

EXPLANATION

  1. The for loop shifts through each of the names in the wordlist: bob, jim, joe, and sam. Each of the names is assigned to the variable person, in turn.

  2. In the body of the loop, each person is sent the contents of the memo file.

  3. The ampersand at the end of the done keyword causes the loop to be executed in the background. The program will continue to run while the loop is executing.

The exec Command and Loops. The exec command can be used to open or close standard input or output without creating a subshell. Therefore, when starting a loop, any variables created within the body of the loop will remain when the loop completes. When using redirection in loops, any variables created within the loop are lost.

The exec command is often used to open files for reading and writing, either by name or by file descriptor number. Recall that file descriptors 0, 1, and 2 are reserved for standard input, output, and error. If a file is opened, it will receive the next available file descriptor. For example, if file descriptor 3 is the next free descriptor, the new file will be assigned file descriptor 3.

Example 8.88
(The File) 1   $ cat tmp     apples     pears     bananas     pleaches     plums (The Script) $ cat speller     #!/bin/sh     # Scriptname: speller     # Purpose: Check and fix spelling errors in a file 2   exec < tmp                   # Opens the tmp file 3   while read line              # Read from the tmp file     do 4       echo $line 5       echo   n  "Is this word correct? [Y/N] " 6       read answer < /dev/tty   # Read from the terminal 7       case  "$answer" in 8       [Yy]*) 9            continue;;         *)              echo  "What is the correct spelling? " 10           read word < /dev/tty 11           sed  "s/$line/$word/g" tmp > error 12           mv error tmp 13           echo $line has been changed to $word.         esac 14  done 

EXPLANATION

  1. The contents of the tmp file are displayed.

  2. The exec command changes standard input (file descriptor 0) so that instead of input coming from the keyboard, it is coming from the tmp file.

  3. The while loop starts. The read command gets a line of input from the tmp file.

  4. The value stored in the line variable is printed.

  5. The user is asked if the word is correct.

  6. The read gets the user's response from the terminal, /dev/tty. If the input is not redirected directly from the terminal, it will continue to be read from the file tmp, still opened for reading.

  7. The case command evalutates the user's answer.

  8. If the variable answer evaluates to a string starting with a Y or y, the continue statement on the next line will be executed.

  9. The continue statement causes the program to go to the beginning of the while loop on line 3.

  10. The user is again asked for input (the correct spelling of the word). The input is redirected from the terminal, /dev/tty.

  11. The sed command will replace the value of line with the value of word wherever it occurs in the tmp file, and send the output to the error file.

  12. The error file will be renamed tmp, thus overwriting the old contents of tmp with the contents of the error file.

  13. This line is displayed to indicate that the change has been made.

  14. The done keyword marks the end of the loop body.

IFS and Loops. The shell's internal field separator (IFS) evaluates to spaces, tabs, and the newline character. It is used as a word (token) separator for commands that parse lists of words, such as read, set, and for. It can be reset by the user if a different separator will be used in a list. Before changing its value, it is a good idea to save the original value of the IFS in another variable. Then it is easy to return to its default value, if needed.

Example 8.89
(The Script ) $ cat runit     #/bin/sh     # Script is called runit.     # IFS is the internal field separator and defaults to     # spaces, tabs, and newlines.     # In this script it is changed to a colon. 1   names=Tom:Dick:Harry:John 2   OLDIFS="$IFS"  # Save the original value of IFS 3   IFS=":" 4   for persons in $names     do 5       echo  Hi $persons     done 6   IFS="$OLDIFS"             # Reset the IFS to old value 7   set Jill Jane Jolene      # Set positional parameters 8   for girl in $*     do 9      echo Howdy $girl     done (The Output) $ runit 5   Hi Tom     Hi Dick     Hi Harry     Hi John 9   Howdy Jill     Howdy Jane     Howdy Jolene 

EXPLANATION

  1. The names variable is set to the string Tom:Dick:Harry:John. Each of the words is separated by a colon.

  2. The value of IFS, whitespace, is assigned to another variable, OLDIFS. Since the value of the IFS is whitespace, it must be quoted to preserve it.

  3. The IFS is assigned a colon. Now the colon is used to separate words.

  4. After variable substitution, the for loop will iterate through each of the names, using the colon as the internal field separator between the words.

  5. Each of the names in the wordlist are displayed.

  6. The IFS is reassigned its original value stored in OLDIFS.

  7. The positional parameters are set. $1 is assigned Jill, $2 is assigned Jane, and $3 is assigned Jolene.

  8. $* evaluates to all the positional parameters, Jill, Jane, and Jolene. The for loop assigns each of the names to the girl variable, in turn, through each iteration of the loop.

  9. Each of the names in the parameter list is displayed.

8.2.8 Functions

Functions were introduced to the Bourne shell in AT&T's UNIX System VR2. A function is a name for a command or group of commands. Functions are used to modularize your program and make it more efficient. You may even store functions in another file and load them into your script when you are ready to use them.

Here is a review of some of the important rules about using functions.

  1. The Bourne shell determines whether you are using a built-in command, a function, or an executable program found out on the disk. It looks for built-in commands first, then functions, and last, executables.

  2. A function must be defined before it is used.

  3. The function runs in the current environment; it shares variables with the script that invoked it, and lets you pass arguments by assigning them as positional parameters. If you use the exit command in a function, you exit the entire script. If, however, either the input or output of the function is redirected or the function is enclosed within backquotes (command substitution), a subshell is created and the function and its variables and present working directory are known only within the subshell. When the function exits, any variables set there will be lost, and if you have changed directories, you will revert to the directory you were in before invoking the function. If you exit the function, you return to where the script left off when the function was invoked.

  4. The return statement returns the exit status of the last command executed within the function or the value of the argument given, and cannot exceed a value of 255.

  5. Functions exist only in the shell where they are defined; they cannot be exported to subshells. The dot command can be used to execute functions stored in files.

  6. To list functions and definitions, use the set command.

  7. Traps, like variables, are global within functions. They are shared by both the script and the functions invoked in the script. If a trap is defined in a function, it is also shared by the script. This could have unwanted side effects.

  8. If functions are stored in another file, they can be loaded into the current script with the dot command.

FORMAT

function_name () { commands ; commands; } 
Example 8.90
dir () { echo "Directories: " ; ls -l | nawk '/^d/ {print $NF}' ; } 

EXPLANATION

The name of the function is dir. The empty parentheses are necessary syntax for naming the function but have no other purpose. The commands within the curly braces will be executed when dir is typed. The purpose of the function is to list only the subdirectories below the present working directory. The spaces surrounding the curly braces are required.

To Unset a Function. To remove a function from memory, the unset command is used.

FORMAT

unset function_name 

Function Arguments and the Return Value. Since the function is executed within the current shell, the variables will be known to both the function and the shell. Any changes made to your environment in the function will also be made to the shell. Arguments can be passed to functions by using positional parameters. The positional parameters are private to the function; that is, arguments to the function will not affect any positional parameters used outside the function.

The return command can be used to exit the function and return control to the program at the place where the function was invoked. (Remember, if you use exit anywhere in your script, including within a function, the script terminates.) The return value of a function is really just the value of the exit status of the last command in the script, unless you give a specific argument to the return command. If a value is assigned to the return command, that value is stored in the ? variable and can hold an integer value between zero and 255. Because the return command is limited to returning only an integer between zero and 255, you can use command substitution to capture the output of a function. Place the entire function in backquotes and assign the output to a variable just as you would if getting the output of a UNIX command.

Example 8.91
(Using the return Command) (The Script) $ cat do_increment     #!/bin/sh     # Scriptname: do_increment 1    increment ()  { 2       sum='expr $1 + 1' 3       return $sum  # Return the value of sum to the script.     } 4   echo   n "The sum is " 5   increment 5    # Call function increment; pass 5 as a                    # parameter. 5 becomes $1 for the increment                    # function. 6   echo $?        # The return value is stored in $? 7   echo  $sum     # The variable "sum" is known to the function,                    # and is also known to the main script. (The Output)       $ do_increment 4,6   The sum is 6 7     6 

EXPLANATION

  1. The function called increment is defined.

  2. When the function is called, the value of the first argument, $1, will be incremented by one and the result assigned to sum.

  3. The return built-in command, when given an argument, returns to the main script after the line where the function was invoked. It stores its argument in the ? variable.

  4. The string is echoed to the screen.

  5. The increment function is called with an argument of 5.

  6. When the function returns, its exit status is stored in the ? variable. The exit status is the exit value of the last command executed in the function unless an explicit argument is used in the return statement. The argument for return must be an integer between 0 and 255.

  7. Although the sum was defined in the function increment, it is global in scope, and therefore also known within the script that invoked the function. Its value is printed.

Example 8.92
(Using Command Substitution) (The Script) $ cat do_square     #!/bin/sh     # Scriptname: do_square 1   function square {     sq='expr $1 \* $1'     echo  "Number to be squared is $1." 2   echo  "The result is $sq "     } 3   echo "Give me a number to square. "     read number 4   value_returned='square $number' # Command substitution 5   echo   $value_returned (The Output) $ do_square 3   Give me a number to square.     10 5   Number to be squared is 10. The result is 100 

EXPLANATION

  1. The function called square is defined. Its function, when called, is to multiply its argument, $1, by itself.

  2. The result of squaring the number is printed.

  3. The user is asked for input. This is the line where the program starts executing.

  4. The function square is called with a number (input from the user) as its argument. Command substitution is performed because the function is enclosed in backquotes. The output of the function, both of its echo statements, is assigned to the variable value_returned.

  5. The command substitution removes the newline between the strings Number to be squared is 10. and The result is 100.

8.2.9 Functions and the dot Command

Storing Functions. Functions are often defined in the .profile file, so that when you log in, they will be defined. Functions cannot be exported, but they can be stored in a file. Then when you need the function, the dot command is used with the name of the file to activate the definitions of the functions within it.

Example 8.93
1   $ cat myfunctions 2   go()  {         cd $HOME/bin/prog         PS1=''pwd' > '          ls     } 3   greetings() { echo "Hi $1! Welcome to my world." ; } 4   $ . myfunctions 5   $ greetings george     Hi george! Welcome to my world. 

EXPLANATION

  1. The file myfunctions is displayed. It contains two function definitions.

  2. The first function defined is called go. It sets the primary prompt to the present working directory.

  3. The second function defined is called greetings. It will greet the name of the user passed in as an argument.

  4. The dot command loads the contents of the file myfunctions into the shell's memory. Now both functions are defined for this shell.

  5. The greetings function is invoked and executed.

Example 8.94
(The .dbfunctions file  shown below contains functions to be used by the main program) 1   $ cat .dbfunctions 2   addon () {    # Function is named and defined in file .dbfunctions 3       while true         do             echo "Adding information "             echo "Type the full name of employee "             read name             echo "Type address for employee "             read address             echo "Type start date for employee (4/10/88 ) :"             read startdate             echo $name:$address:$startdate             echo  n "Is this correct? "             read ans             case "$ans"  in             [Yy]*)                       echo "Adding info..."                       echo $name:$address:$startdate>>datafile                  sort  u datafile  o datafile                  echo  n "Do you want to go back to the main menu? "                  read ans                  if [ $ans = Y  o $ans = y ]                  then 4                     return        # Return to calling program                  else 5                     continue      # Go to the top of the loop                  fi                  ;;             *)                  echo "Do you want to try again? "                  read answer             case "$answer" in             [Yy]*) continue;;             *) exit;;             esac                  ;;         esac     done 6   }   # End of function definition ------------------------------------------------------------- (The Command Line) 7   $ more mainprog     #!/bin/sh     # Scriptname: mainprog     # This is the main script that will call the function, addon     datafile=$HOME/bourne/datafile 8   . .dbfunctions      # The dot command reads the dbfunctions file                         # into memory     if [ !  f $datafile ]     then        echo "'basename $datafile' does not exist" 1>&2        exit 1     fi 9   echo "Select one: "     cat <<EOF         [1] Add info         [2] Delete info         [3] Exit     EOF     read choice     case $choice in 10      1)  addon          # Calling the addon function              ;;         2)  delete         # Calling the delete function              ;;         3)  update              ;;         4)             echo Bye             exit 0              ;;         *)  echo Bad choice             exit 2              ;;     esac     echo Returned from function call     echo The name is $name     # Variable set in the function are known in this shell. 

EXPLANATION

  1. The .dbfunctions file is displayed.

  2. The addon function is defined. Its function is to add new information to the file datafile.

  3. A while loop is entered. It will loop forever unless a loop control statement such as break or continue is included in the body of the loop.

  4. The return command sends control back to the calling program where the function was called.

  5. Control is returned to the top of the while loop.

  6. The closing curly brace ends the function definition.

  7. This is the main script. The function addon will be used in this script.

  8. The dot command loads the file .dbfunctions into the program's memory. Now the function addon is defined for this script and available for use. It is as though you had just defined the function right here in the script.

  9. A menu is displayed with the here document. The user is asked to select a menu item.

  10. The addon function is invoked.

8.2.10 Trapping Signals

While your program is running, if you press Control-C or Control-\, your program terminates as soon as the signal arrives. There are times when you would rather not have the program terminate immediately after the signal arrives. You could arrange to ignore the signal and keep running or perform some sort of cleanup operation before actually exiting the script. The trap command allows you to control the way a program behaves when it receives a signal.

A signal is defined as an asynchronous message that consists of a number that can be sent from one process to another, or by the operating system to a process if certain keys are pressed or if something exceptional happens.[9] The trap command tells the shell to terminate the command currently in execution upon the receipt of a signal. If the trap command is followed by commands within quotes, the command string will be executed upon receipt of a specified signal. The shell reads the command string twice, once when the trap is set, and again when the signal arrives. If the command string is surrounded by double quotes, all variable and command substitution will be performed when the trap is set the first time. If single quotes enclose the command string, variable and command substitution do not take place until the signal is detected and the trap is executed.

Use the command kill l to get a list of all signals. Table 8.15 provides a list of signal numbers and their corresponding names.

FORMAT

trap 'command; command' signal-number 
Example 8.95
trap 'rm tmp*; exit 1'  1 2 15 

EXPLANATION

When any of the signals 1 (hangup), 2 (interrupt), or 15 (software termination) arrive, remove all the tmp files and exit.

If an interrupt signal comes in while the script is running, the trap command lets you handle the signal in several ways. You can let the signal behave normally (default), ignore the signal, or create a handler function to be called when the signal arrives.

Table 8.15. Signal Numbers and Signals
1) HUP 12) SYS 23) POLL
2) INT 13) PIPE 24) XCPU
3) QUIT 14) ALRM 25) XFSZ
4) ILL 15) TERM 26) VTALRM
5) TRAP 16) URG 27) PROF
6) IOT 17) STOP 28) WINCH
7) EMT 18) TSTP 29) LOST
8) FPE 19) CONT 30) USR1
9) KILL 20) CHLD 31) USR2
10) BUS 21) TTIN  
11) SEGV 22) TTOU  

Resetting Signals. To reset a signal to its default behavior, the trap command is followed by the signal name or number.

Example 8.96
trap 2 

EXPLANATION

Resets the default action for signal 2, SIGINT, which is used to kill a process, i.e., Control-C.

Example 8.97
trap 'trap 2' 2 

EXPLANATION

Sets the default action for signal 2 (SIGINT) to execute the command string within quotes when the signal arrives. The user must press Control-C twice to terminate the program. The first trap catches the signal, the second trap resets the trap back to its default action, which is to kill the process.

Ignoring Signals. If the trap command is followed by a pair of empty quotes, the signals listed will be ignored by the process.

Example 8.98
trap " "  1  2 

EXPLANATION

Signals 1 and 2 will be ignored by the shell.

Listing Traps. To list all traps and the commands assigned to them, type trap.

Example 8.99
(The Script) $ cat trapping     #/bin/sh     # Scriptname: trapping     # Script to illustrate the trap command and signals 1   trap 'echo "Control C will not terminate $0."' 2 2   trap 'echo "Control \ will not terminate $0."' 3 3   echo "Enter any string after the prompt."     echo "When you are ready to exit, type \"stop\"." 4   while true     do        echo   n "Go ahead...> "        read reply 5      if [ "$reply" = stop ]        then 6           break        fi 7   done (The Output) $  trapping Enter any string after the prompt. When you are ready to exit, type "stop". Go ahead...> this is it^C Control C will not terminate trapping. Go ahead...> this is never it ^\ Control \ will not terminate trapping. Go ahead...> stop $ 

EXPLANATION

  1. The first trap catches the INT signal, Control-C. If ^C is pressed while the program is running, the command enclosed in quotes will be executed. Instead of aborting, the program will print Control-C will not terminate trapping, and continue to prompt the user for input.

  2. The second trap command will be executed when the user presses Conrtrol-\, the QUIT signal. The string Control-\ will not terminate trapping will be displayed, and the program will continue to run. This signal, by default, kills the process and produces a core file.

  3. The user is prompted for input.

  4. The while loop is entered and a prompt, Go ahead >, is displayed.

  5. The user input is assigned to the reply variable and, if its value matches stop, the loop exits and the program terminates. This is the way we will get out of this program unless it is killed with the kill command.

  6. The break command causes the body of the loop to be exited with control starting after line 7. In this case, the program is at its end.

  7. This is the end of the while loop.

Traps in Functions. If you use a trap to handle a signal in a function, it will affect the entire script, once the function is called. The trap is global to the script. In the following example, the trap is set to ignore the interrupt key, ^C. This script had to be killed with the kill command to stop the looping. It demonstrates potential undesirable side effects when using traps in functions.

Example 8.100
(The Script)    #!/bin/sh 1  trapper () {           echo "In trapper" 2      trap 'echo "Caught in a trap!"' 2        # Once set, this trap affects the entire script. Anytime        # ^C is entered, the script will ignore it.    } 3  while :    do       echo "In the main script" 4     trapper 5     echo "Still in main"       sleep 5   done (The Output) $ trapper In the main script In trapper Still in main ^CCaught in a trap! In the main script In trapper Still in main ^CCaught in a trap! In the main script 

EXPLANATION

  1. The trapper function is defined. All variables and traps set in the function are global to the script.

  2. The trap command will ignore signal 2, the interrupt key (^C). If ^C is pressed, the message Caught in a trap is printed, and the script continues forever.

  3. The main script starts a forever loop.

  4. The function trapper is called.

  5. When the function returns, execution starts here.

Debugging. By using the n option to the sh command, you can check the sytnax of your scripts without really executing any of the commands. If there is a syntax error in the script, the shell will report the error. If there are no errors, nothing is displayed.

The most commonly used method for debugging scripts is to use the set command with the x option, or to use the x option as an argument to the sh command, followed by the script name. See Table 8.16 for a list of debugging options. These options allow an execution trace of your script. Each command from your script is displayed after substitution has been performed, and then the command is executed. When a line from your script is displayed, it is preceded with a plus (+) sign.

With the verbose option turned on, or by invoking the Bourne shell with the v option (sh v scriptname), each line of the script will be displayed just as it was typed in the script, and then executed.

Table 8.16. Debugging Options
Command Option What It Does
sh x scriptname Echo option Displays each line of script after variable substitutions and before execution.
sh v scriptname Verbose option Displays each line of script before execution, just as you typed it.
sh n scriptname Noexec option Interprets but does not execute commands.
set x Turns on echo Traces execution in a script.
set +x Turns off echo Turns off tracing.
Example 8.101
(The Script) $ cat todebug     #!/bin/sh 1   # Scriptname: todebug     name="Joe Blow"     if  [ "$name" = "Joe Blow" ]     then        echo "Hi $name"     fi     num=1     while [ $num -lt 5 ]     do          num='expr $num + 1'     done     echo The grand total is $num (The Output) 2   $ sh  x todebug     + name=Joe Blow     + [ Joe Blow =  Joe Blow ]     + echo Hi Joe Blow     Hi Joe Blow     num=1     + [ 1 -lt 5 ]     + expr 1 + 1     num=2     + [ 2 -lt 5 ]     + expr 2 + 1     num=3     + [ 3 -lt 5 ]     + expr 3 + 1      num=4     + [ 4 -lt 5 ]     + expr 4 + 1      num=5     + [ 5 -lt 5 ]     + echo The grand total is 5     The grand total is 5 

EXPLANATION

  1. The script is called todebug. You can watch the script run with the x switch turned on. Each iteration of the loop is displayed and the values of variables are printed as they are set and when they change.

  2. The sh command starts the Bourne shell with the x option. Echoing is turned on. Each line of the script will be displayed to the screen prepended with a plus sign (+). Variable substitution is performed before the line is displayed. The result of the execution of the command appears after the line has been displayed.

8.2.11 Processing Command Line Options with getopts

If you are writing scripts that require a number of command line options, positional parameters are not always the most efficient. For example, the UNIX ls command takes a number of command line options and arguments. (An option requires a leading dash; an argument does not.) Options can be passed to the program in several ways: ls laFi, ls i a l F, ls ia F, and so forth. If you have a script that requires arguments, positional parameters might be used to process the arguments individually, such as ls l i F . Each dash option would be stored in $1, $2, and $3, respectively. But, what if the user listed all of the options as one dash option, as in ls liF? Now the liF would all be assigned to $1 in the script. The getopts function makes it possible to process command line options and arguments in the same way they are processed by the ls program.[10] The getopts function will allow the runit program to process its arguments using any variety of combinations.

Example 8.102
(The Command Line ) 1   $ runit  x  n  200 filex 2   $ runit  xn200 filex 3   $ runit  xy 4   $ runit  yx  n 30 5   $ runit  n250  xy filey ( any other combination of these arguments ) 

EXPLANATION

  1. The program runit takes four arguments: x is an option, n is an option requiring a number argument after it, and filex is an argument that stands alone.

  2. The program runit combines the options x and n and the number argument 200; filex is also an argument.

  3. The program runit is invoked with the x and y options combined.

  4. The program runit is invoked with the y and x options combined; the n option is passed separately, as is the number argument, 30.

  5. The program runit is invoked with the n option combined with the number argument, the x and y options are combined, and the filey is separate.

Before getting into all the details of the runit program, we examine the line from the program where getopts is used to see how it processes the arguments.

Example 8.103
(A Line from the Script Called "runit") while getopts :xyn: name 

EXPLANATION

  • x, y, and n are the options.

  • Options typed at the command line begin with a dash.

  • Any options that do not contain a dash tell getopts that the option list is at an end.

  • Each time getopts is called, it places the next option it finds, without the dash, in the variable name. (You can use any variable name here.) If an illegal argument is given, name is assigned a question mark.

  • OPTIND is a special variable that is initialized to one and is incremented each time getopts completes processing a command line argument to the number of the next argument getopts will process.

  • The OPTARG variable contains the value of a legal argument.

getopts Scripts. The following examples illustrate how getopts processes arguments.

Example 8.104
(The Script) $ cat opts1    #!/bin/sh    # Program opts1    # Using getopts   First try   1  while getopts xy options    do 2     case $options in 3     x) echo "you entered  x as an option";;       y) echo "you entered  y as an option";;       esac    done (The Command Line) 4  $ opts1  x    you entered  x as an option 5  $ opts1  xy    you entered  x as an option    you entered  y as an option 6  $ opts1  y    you entered  y as an option 7  $ opts1  b    opts1:   illegal option --  b 8  $ opts1 b 

EXPLANATION

  1. The getopts command is used as a condition for the while command. The valid options for this program are listed after the getopts command; they are x and y. Each option is tested in the body of the loop, one after the other. Each option will be assigned to the variable options, without the leading dash. When there are no longer any arguments to process, getopts will exit with a nonzero status, causing the while loop to terminate.

  2. The case command is used to test each of the possible options found in the options variable, either x or y.

  3. If x was an option, the string You entered x as an option is displayed.

  4. At the command line, the opts1 script is given a x option, a legal option to be processed by getopts.

  5. At the command line, the opts1 script is given a xy option; x and y are legal options to be processed by getopts.

  6. At the command line, the opts1 script is given a y option, a legal option to be processed by getopts.

  7. The opts1 script is given a b option, an illegal option. Getopts sends an error message to stderr.

  8. An option without a dash prepended to it is not an option and causes getopts to stop processing arguments.

Example 8.105
(The Script) $ cat opts2    #!/bin/sh    # Program opts2    # Using getopts   Second try   1  while getopts xy options 2> /dev/null    do 2     case $options in       x) echo "you entered  x as an option";;       y) echo "you entered  y as an option";; 3     \?) echo "Only -x and -y are valid options"  1>&2;;       esac    done (The Command Line)    $ opts2  x    you entered  x as an option    $ opts2  y    you entered  y as an option    $ opts2 xy    $ opts2  xy    you entered  x as an option    you entered  y as an option 4  $ opts2  g    Only -x and -y are valid options 5  $ opts2  c     Only -x and -y are valid options 

EXPLANATION

  1. If there is an error message from getopts, it is redirected to /dev/null.

  2. If the option is a bad option, a question mark will be assigned to the options variable. The case command can be used to test for the question mark, allowing you to print your own error message to standard error.

  3. If the options variable is assigned the question mark, the case statement is executed. The question mark is protected with the backslash so that the shell does not see it as a wildcard and try to perform filename substitution.

  4. g is not a legal option. A question mark is assigned to the variable options, and the error message is displayed.

  5. c is not a legal option. A question mark is assigned to the variable options, and the error message is displayed.

Example 8.106
(The Script) $ cat opts3    #!/bin/sh    # Program opts3    # Using getopts   Third try   1  while getopts dq: options    do        case $options in 2          d) echo " d is a valid switch ";; 3          q) echo "The argument for -q is $OPTARG";;            \?) echo "Usage:opts3 -dq filename ... " 1>&2;;       esac    done (The Command Line) 4  $ opts3  d     d is a valid switch 5  $ opts3 -q  foo    The argument for -q is foo 6  $ opts3 -q    Usage:opts3 -dq filename ... 7  $ opts3  e    Usage:opts3 -dq filename ... 8  $ opts3 e 

EXPLANATION

  1. The while command tests the exit status of getopts; if getopts can successfully process an argument, it returns zero exit status, and the body of the while loop is entered. The colon appended to the argument list means that the q option requires an argument. The argument will be stored in the special variable, OPTARG.

  2. One of the legal options is d. If d is entered as an option, the d (without the dash) is stored in the options variable.

  3. One of the legal options is q. The q option requires an argument. There must be a space between the q option and its argument. If q is entered as an option followed by an argument, the q, without the dash, is stored in the options variable and the argument is stored in the OPTARG variable. If an argument does not follow the q option, the question mark is stored in the variable options.

  4. The d option is a legal option to opts3.

  5. The q option with an argument is also a legal option to opts3.

  6. The q option without an argument is an error.

  7. The e option is invalid. A question mark is stored in the options variable if the option is illegal.

  8. The option is prepended with neither a dash nor a plus sign. The getopts command will not process it as an option and returns a nonzero exit status. The while loop is terminated.

Example 8.107
$ cat opts4    #!/bin/sh    # Program opts4    # Using getopts   Fourth try   1  while getopts xyz: arguments 2>/dev/null    do       case $arguments  in 2     x) echo "you entered -x as an option.";;       y) echo "you entered -y as an option.";; 3     z) echo "you entered -z as an option."            echo "\$OPTARG is $OPTARG.";; 4     \?) echo "Usage opts4 [-xy] [-z  argument]"            exit 1;;       esac    done 5  echo "The initial value of \$OPTIND is 1.       The final value of \$OPTIND is $OPTIND.       Since this reflects the number of the next command line       argument,the number of arguments passed was       'expr $OPTIND - 1'. " (The Command Line) $ opts4 -xyz foo you entered -x as an option. you entered -y as an option. you entered -z as an option. $OPTARG is foo. The initial value of $OPTIND is 1. The final value of $OPTIND is 3. Since this reflects the number of the next command line argument, the number of arguments passed was 2. $ opts4 -x -y -z  boo you entered -x as an option. you entered -y as an option. you entered -z as an option. $OPTARG is boo. The initial value of $OPTIND is 1. The final value of $OPTIND is 5. Since this reflects the number of the next command line argument,the number of arguments passed was 4. $ opts4 -d Usage: opts4  [-xy] [-z argument] 

EXPLANATION

  1. The while command tests the exit status of getopts; if getopts can successfully process an argument, it returns zero exit status, and the body of the while loop is entered. The colon appended to the z option tells getopts that an argument must follow the z option. If the option takes an argument, the argument is stored in the getopts built-in variable OPTARG.

  2. If x is given as an option, it is stored in the variable arguments.

  3. If z is given as an option with an argument, the argument is stored in the built-in variable OPTARG.

  4. If an invalid option is entered, the question mark is stored in the variable arguments, and an error message is displayed.

  5. The special getopts variable, OPTIND, holds the number of the next option to be processed. Its value is always one more than the actual number of command line arguments.

8.2.12 The eval Command and Parsing the Command Line

The eval command evaluates a command line, performs all shell substitutions, and then executes the command line. It is used when normal parsing of the command line is not enough.

Example 8.108
1   $ set a b c d 2   $ echo The last argument is \$$# 3   The last argument is $4 4   $ eval echo The last argument is \$$#     The last argument is d 5   $ set -x     $ eval echo The last argument is \$$#     + eval echo the last argument is $4     + echo the last argument is d     The last argument is d 

EXPLANATION

  1. Four positional parameters are set.

  2. The desired result is to print the value of the last positional parameter. The \$ will print a literal dollar sign. The $# evaluates to 4, the number of positional parameters. After the shell evaluates the $#, it does not parse the line again to get the value of $4.

  3. $4 is printed instead of the last argument.

  4. After the shell performs variable substitution, the eval command performs the variable substitution and then executes the echo command.

  5. Turn on the echoing to watch the order of parsing.

Example 8.109
(From SVR4 Shutdown Program) 1  eval '/usr/bin/id | /usr/bin/sed 's/[^a-z0-9=].*//'' 2  if [ "${uid:=0}" -ne 0 ]    then 3      echo $0: Only root can run $0        exit 2    fi 

EXPLANATION

  1. This is a tricky one. The id program's output is sent to sed to extract the uid part of the string. The output for id is:

    uid=9496(ellie) gid=40 groups=40 uid=0(root) gid=1(daemon) groups=1(daemon) 

    The sed regular expression reads: Find any character that is not a letter, number, or an equal sign and remove that character and all characters following it. The result is to substitute everything from the first opening parenthesis to the end of the line with nothing. What is left is either uid=9496 or uid=0.

    After eval evaluates the command line, it then executes the resulting command:

    uid=9496 

    or

    uid=0 

    For example, if the user's ID is root, the command executed would be uid=0. This creates a local variable in the shell called uid and assigns zero to it.

  2. The value of the uid variable is tested for zero, using command modifiers.

  3. If the uid is not zero, the echo command displays the script name ($0) and the message.

8.2.13 Shell Invocation Options

When the shell is started using the sh command, it can take options to modify its behavior. See Table 8.17.

Table 8.17. Shell Invocation Options
Option Meaning
i Shell is in the interactive mode. QUIT and INTERRUPT are ignored.
s Commands are read from standard input and output is sent to standard error.
c string Commands are read from string.

8.2.14 The set Command and Options

The set command can be used to turn shell options on and off, as well as for handling command line arguments. To turn an option on, the dash ( ) is prepended to the option; to turn an option off, the plus sign (+) is prepended to the option. See Table 8.18 for a list of set options.

Example 8.110
1   $ set -f 2   $ echo *     * 3   $ echo ??     ?? 4   $ set +f 

EXPLANATION

  1. The f option is turned on, disabling filename expansion.

  2. The asterisk is not expanded.

  3. The question marks are not expanded.

  4. The f is turned off; filename expansion is enabled.

Table 8.18. The set Command Options
Option Meaning
a Marks variables that have been modified or exported.
e Exits the program if a command returns a nonzero status.
f Disables globbing (filename expansion).
h Locates and remembers function commands as functions when they are defined, not just when they are executed.
k All keyword arguments are placed in the environment for a command, not just those that precede the command name.
n Reads commands but does not execute them; used for debugging.
t Exits after reading and executing one command.
u Treats unset variables as an error when performing substitution.
v Prints shell input lines as they are read; used for debugging.
x Prints commands and their arguments as they are being executed. Used for debugging.
Does not change any of the flags.

8.2.15 Shell Built-In Commands

The shell has a number of commands that are built into its source code. Since the commands are built-in, the shell doesn't have to locate them on disk, making execution much faster. The built-in commands are listed in Table 8.19.

Table 8.19. Built-In Commands
Command What It Does
: Do-nothing command; returns exit status zero.
. file The dot command reads and executes command from file.
break [n] See looping.
continue [n] See looping.
cd Change directory.
echo [ args ] Echo arguments.
eval command Shell scans the command line twice before execution.
exec command Runs command in place of this shell.
exit [ n ] Exit the shell with status n.
export [ var ] Make var known to subshells.
hash Controls the internal hash table for quicker searches for commands.
kill [ signal process ] Sends the signal to the PID number or job number of the process. See /usr/include/sys/signal.h for a list of signals.
getopts Used in shell scripts to parse command line and check for legal options.
login [ username ] Sign onto the system.
newgrp [ arg ] Logs a user into a new group by changing the real group and effective group ID.
pwd Print present working directory.
read [ var ] Read line from standard input into variable var.
readonly [ var ] Make variable var read-only. Cannot be reset.
return [ n ] Return from a function where n is the exit value given to the return.
set See Table 8.18.
shift [ n ] Shift positional parameters to the left n times.
stop pid Halt execution of the process number PID.
suspend Stops execution of the current shell (but not if a login shell).
times Print accumulated user and system times for processes run from this shell.
trap [ arg ] [ n ] When shell receives signal n ( 0, 1, 2, or 15 ), execute arg.
type [ command ] Prints the type of command; e.g., pwd has a built-in shell, in ksh, an alias for the command whence v.
umask [ octal digits ] User file creation mode mask for owner, group, and others.
unset [ name ] Unset value of variable or function.
wait [ pid#n ] Wait for background process with PID number n and report termination status.
ulimit [ options size ] Set maximum limits on processes.
umask [ mask ] Without argument, print out file creation mask for permissions.

BOURNE SHELL LAB EXERCISES

Lab 8: Getting Started

1:

What process puts the login prompt on your screen?

2:

What process assigns values to HOME, LOGNAME, and PATH?

3:

How do you know what shell you are using?

4:

Where is your login shell assigned? (What file?)

5:

Explain the difference between the /etc/profile and .profile file. Which one is executed first?

6:

Edit your .profile file as follows:

  1. Welcome the user.

  2. Add your home directory to the path if it is not there.

  3. Set the erase function to the Backspace key using stty.

  4. Type: profile

    What is the function of the dot command?

Lab 9: Shell Metacharacters

1:

Make a directory called wildcards. Cd to that directory and type at the prompt:

touch ab abc a1 a2 a3 all a12 ba ba.1 ba.2 filex filey AbC ABC ABc2 abc 
2:

Write and test the command that will do the following:

  1. List all files starting with a.

  2. List all files ending in at least one digit.

  3. List all files starting with an a or A.

  4. List all files ending in a period, followed by a digit.

  5. List all files containing just two of the letter a.

  6. List three character files where all letters are uppercase.

  7. List files ending in 10, 11, or 12.

  8. List files ending in x or y.

  9. List all files ending in a digit, an uppercase letter, or a lowercase letter.

  10. List all files not starting with a b or B.

  11. Remove two-character files starting with a or A.

Lab 10: Redirection

1:

What are the names of the three file streams associated with your terminal?

2:

What is a file descriptor?

3:

What command would you use to do the following:

  1. Redirect the output of the ls command to a file called lsfile?

  2. Redirect and append the output of the date command to lsfile?

  3. Redirect the output of the who command to lsfile? What happened?

4:

Perform the following:

  1. Type cp all by itself. What happens?

  2. Save the error message from the above example to a file.

  3. Use the find command to find all files, starting from the parent directory, of type directory. Save the standard output in a file called found and any errors in a file called found.errs.

  4. Take the output of three commands and redirect the output to a file called gottem_all.

  5. Use a pipe(s) with the ps and wc commands to find out how many processes you are currently running.

Lab 11: First Script

1:

Write a script called greetme that will do the following:

  1. Contain a comment section with your name, the name of this script, and the purpose of this script.

  2. Greet the user.

  3. Print the date and the time.

  4. Print a calendar for this month.

  5. Print the name of your machine.

  6. Print the name and release of this operating system, (cat /etc/motd).

  7. Print a list of all files in your parent directory.

  8. Print all the processesroot is running.

  9. Print the value of the TERM, PATH, and HOME variables.

  10. Print your disk usage (du).

  11. Use theid command to print your group ID.

  12. Print Please couldn't you loan me $50.00?

  13. Tell the user Good bye and the current hour (see man pages for the date command).

2:

Make sure your script is executable.

chmod +x greetme 
3:

What was the first line of your script? Why do you need this line?

Lab 12: Command Line Arguments

1:

Write a script called rename that will take two arguments: the first argument is the name of the original file and the second argument is the new name for the file.

If the user does not provide two arguments, a usage message will appear on the screen and the script will exit. Here is an example of how the script works:

$ rename Usage: rename oldfilename newfilename $ $ rename file1 file2 file1 has been renamed file2 Here is a listing of the directory: a file2 b file.bak 
2:

The following find command (SunOS) will list all files in the root partition that are larger than 100K and that have been modified in the last week. (Check your man pages for the correct find syntax on your system.)

find /  xdev  mtime  7  size +200  print 

Write a script called bigfiles that will take two arguments: One will be the mtime and one the size value. An appropriate error message will be sent to stderr if the user does not provide two arguments.

3:

If you have time, write a script called vib that creates backup files for vi. The backup files will have the extension .bak appended to the original name.

Lab 13: Getting User Input

1:

Write a script called nosy that will do the following:

  1. Ask the user's full name first, last, and middle name.

  2. Greet the user by his or her first name.

  3. Ask the user's year of birth and calculate his or her age (useexpr).

  4. Ask the user's login name and print his or her user ID (from /etc/passwd).Tell the user his or her home directory.

  5. Show the user the processes he or she is running.

  6. Tell the user the day of the week, and the current time in nonmilitary time. The output should resemble

    The day of the week is Tuesday and the current time is 04:07:38 PM.

2:

Create a text file called datafile (unless this file has already been provided for you). Each entry consists of fields separated by colons. The fields are as follows:

  1. First and last name

  2. Phone number

  3. Address

  4. Birth date

  5. Salary

3:

Create a script called lookup that will do the following:

  1. Contain a comment section with the script name, your name, the date, and the reason for writing this script. The reason for writing this script is to display the datafile in sorted order.

  2. Sort the datafile by last names.

  3. Show the user the contents of the datafile.

  4. Tell the user the number of entries in the file.

4:

Try the x and v options for debugging your script. How did you use these commands? How do they differ?

Lab 14: Conditional Statements

1:

Write a script called checking that will do the following:

  1. Take a command line argument: a user's login name.

  2. Test to see if a command line argument was provided.

  3. Check to see if the user is in the /etc/passwd file. If so, it will print

Found <user> in the /etc/passwd file. 

Otherwise, it will print

No such user on our system. 
2:

In the lookup script, ask the user if he or she would like to add an entry to the datafile. If the answer is yes or y:

  1. Prompt the user for a new name, phone, address, birth date, and salary. Each item will be stored in a separate variable. You will provide the colons between the fields and append the information to the datafile.

  2. Sort the file by last names. Tell the user you added the entry, and show him or her the line preceded by the line number.

Lab 15: Conditionals and File Testing

1:

Rewrite checking. After checking whether the named user is in the /etc/passwd file, the program will check to see if the user is logged on. If so, the program will print all the processes that are running; otherwise, it will tell the user

<user> is not logged on. 
2:

The lookup script depends on the datafile in order to run. In the lookup script, check to see if the datafile exists and if it is readable and writeable. Add a menu to the lookup script to resemble the following:

  1. Add entry.

  2. Delete entry.

  3. View entry.

  4. Exit.

    1. You already have the Add entry part of the script written. The Add entry routine should now include code that will check to see if the name is already in the datafile and if it is, tell the user so. If the name is not there, add the new entry.

    2. Now write the code for the Delete entry, View entry, and Exit functions.

    3. The Delete part of the script should first check to see if the entry exists before trying to remove it. If it does not exist, notify the user; otherwise, remove the entry and tell the user you removed it. On exit, make sure that you use a digit to represent the appropriate exit status.

    4. How do you check the exit status from the command line?

Lab 16: The Case Statement

1:

The ps command is different on UCB and AT&T UNIX. On AT&T UNIX, the command to list all processes is

ps  ef 

On UCB UNIX, the command is

ps  aux 

Write a program called systype that will check for a number of different system types. The cases to test for will be

AIX

IRIX

HP UX

SCO

OSF1

ULTRIX

SunOS (Solaris / SunOS)

OS

Solaris, HP UX, SCO, and IRIX are AT&T-type systems. The rest are BSDish.

The version of UNIX you are using will be printed to stdout. The system name can be found with the uname s command or from the /etc/motd file.

Lab 17: Loops

Select one of the following:

1:

Write a program called mchecker to check for new mail and write a message to the screen if new mail has arrived.

  1. The program will get the size of the mail spool file for the user. (The spool files are found in /usr/mail/$LOGNAME on AT&T systems and /usr/spool/mail/$USER on UCB systems. Use the find command if you cannot locate the file.) The script will execute in a continuous loop, once every 30 seconds. Each time the loop executes, it will compare the size of the mail spool file with its size from the previous loop. If the new size is greater than the old size, a message will be printed on your screen, saying Username, You have new mail.

The size of a file can be found by looking at the output fromls l, wc c or from the find command.

2:

Write a program called dusage that will mail a list of users, one at a time, a listing of the number of blocks they are currently using. The list of users will be in a file called potential_hogs. One of the users listed in the potential_hogs file will be admin.

  1. Use file testing to check that potential_hogs file exists and is readable.

  2. A loop will be used to iterate through the list of users. Only those users who are using over 500 blocks will be sent mail. The user admin will be skipped over (i.e., he or she does not get a mail message). The mail message will be stored in a here document in your dusage script.

  3. Keep a list of the names of each person who received mail. Do this by creating a log file. After everyone on the list has been sent mail, print the number of people who received mail and a list of their names.

Lab 18: Functions

1:

Rewrite the systype program from Lab 9 as a function that returns the name of the system. Use this function to determine what options you will use with the ps command in the checking program.

2:

The ps command to list all processes on AT&T UNIX is

ps  ef 
3:

On BSD UNIX, the command is

ps  aux 
4:

Write a function called cleanup that will remove all temporary files and exit the script. If the interrupt or hangup signal is sent while the program is running, the trap command will call the cleanup function.

5:

Use a here document to add a new menu item to the lookup script to resemble the following:

  1. Add entry

  2. Delete entry

  3. Change entry

  4. View entry

  5. Exit

Write a function to handle each of the items in the menu. After the user has selected a valid entry and the function has completed, ask if the user would like to see the menu again. If an invalid entry is entered, the program should print

Invalid entry, try again. 

and the menu will be redisplayed.

6:

Create a submenu under View entry in the lookup script. The user will be asked if he or she would like to view specific information for a selected individual:

  1. Phone

  2. Address

  3. Birthday

  4. Salary

7:

Use the trap command in a script to perform a cleanup operation if the interrupt signal is sent while the program is running.

[1]  If the .profile were executed directly as a script, a subshell would be started. Then the variables would be set in the subshell, but not in the login shell (parent shell).

[2]  A variable set to some value or to null will be displayed by using the set command, whereas an unset variable will not.

[3]  The Korn shell allows backquotes for command substitution for upward-compatibility, but provides an alternate method as well.

[4]  Remember, without arguments, the set command displays all the variables that have been set for this shell, local and exported. With options, the set command turns on and off shell control options such as x and v.

[5]  Unlike the C shell, the Bourne shell does not support an if statement without a then, even for a simple statement.

[6]  If using NIS+, the command would read: If nismatch "$name" passwd.org_dir.

[7]  If the continue command is given a number higher than the number of loops, the loop exits.

[8]  $$ expands to the PID number of the current shell. By appending this number to the filename, the filename is made unique.

[9]  Bolsky, Morris I., and Korn, David G., The New KornShell Command and Programming Language (Englewood Cliffs, NJ: Prentice Hall PTR, 1995), p. 327.

[10]  See the UNIX manual pages (Section 3) for the C library function getopt.

CONTENTS


UNIX Shells by Example
UNIX Shells by Example, 3rd Edition
ISBN: 013066538X
EAN: 2147483647
Year: 2001
Pages: 18
Authors: Ellie Quigley

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net