CONTENTS |
If the Bourne shell is your login shell, it follows a chain of processes before you see a shell prompt.
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.
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.)
(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
|
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.
(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
|
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.
1 $ PS1="'uname -n > '" 2 chargers >
EXPLANATION
|
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.
1 $ echo "Hello 2 > there" 3 Hello there 4 $ 5 $ PS2=" > " 6 $ echo 'Hi 7 > > > there' Hi there $
EXPLANATION
|
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.
(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
|
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.
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
|
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.
$ . .profile
EXPLANATIONThe 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] |
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.
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
|
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.
$ ls; pwd; date
EXPLANATIONThe 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.
$ ( ls ; pwd; date ) > outputfile
EXPLANATIONThe 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.
$ cc prgm1.c o prgm1 && prgm1
EXPLANATIONIf 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. |
$ cc prog.c >& err || mail bob < err
EXPLANATIONIf 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.
1 $ man sh | lp& 2 [1] 1557 3 $ kill -9 $!
EXPLANATION
|
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.
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. |
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.
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.
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
|
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.
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
|
The Square Brackets. The brackets are used to match filenames containing one character in a set or range of characters.
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
|
Escaping Metacharacters. To use a metacharacter as a literal character, the backslash may be used to prevent the metacharacter from being interpreted.
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
|
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
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
|
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.
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
|
Setting Read-Only Variables. A read-only variable cannot be redefined or unset.
1 $ name=Tom 2 $ readonly name $ echo $name Tom 3 $ unset name name: readonly 4 $ name=Joe name: readonly
EXPLANATION
|
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.
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. |
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.)
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
|
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.
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
|
Unsetting Variables. Both local and environment variables can be unset by using the unset command, unless the variables are set as read-only.
unset name; unset TERM
EXPLANATIONThe 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.
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. |
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
|
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.
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.
(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
|
(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
|
(Assigning Temporary Alternate Value) 1 $ foo=grapes 2 $ echo ${foo:+pears} pears 3 $ echo $foo grapes $
EXPLANATION
|
(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
|
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.
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". |
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
|
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.
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 |
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
|
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.
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.
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
|
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.
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
|
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.
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
|
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]
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
|
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.
FORMATfunction_name () { commands ; commands; } |
1 $ greet () { echo "Hello $LOGNAME, today is 'date'; } 2 $ greet Hello ellie, today is Thu Oct 4 19:56:31 PDT 2001
EXPLANATION
|
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
|
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.
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.
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. |
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
|
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".
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. |
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 $ 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 $ 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
|
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.
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
|
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.
EXPLANATION
|
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
|
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.
1 $ chmod +x myscript 2 $ ls -lF myscript -rwxr-xr-x 1 ellie 0 Jul 13:00 myscript*
EXPLANATION
|
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.
(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
|
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".
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. |
(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
|
(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
|
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.
Operator | Function |
---|---|
* / % | Multiplication, division, modulus. |
+ - | Addition, substraction. |
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
|
Floating Point Arithmetic. The bc, awk, and nawk utilities are useful if you need to perform more complex calculations.
(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
|
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.
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. |
(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
|
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.
(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
|
(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
|
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.
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
|
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.
(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
|
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.
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]
FORMATif commandthen then command command fi --------------------------------- if test expression then command fi or if [ expression ] then command fi ------------------------------- |
1 if ypmatch "$name" passwd > /dev/null 2>&1 2 then echo Found $name! 3 fi
EXPLANATION
|
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
|
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.
(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
|
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.
(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
|
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.
FORMATif command then command(s) else command(s) fi |
(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
|
(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
|
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.
FORMATif command then command(s) elif command then commands(s) elif command then command(s) else command(s) fi |
(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
|
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.
EXPLANATION
|
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.
(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
|
(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
|
(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
|
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.
FORMATcase variable in value1) command(s) ;; value2) command(s) ;; *) command(s) ;; esac |
(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
|
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.
(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
|
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.
FORMATfor variable in word_list do command(s) done |
(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
|
(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
|
(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
|
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.
(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
|
(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
|
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.
FORMATwhile command do command(s) done |
(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
|
(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
|
(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
|
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.
FORMATuntil command do command(s) done |
#!/bin/sh 1 until who | grep linda 2 do sleep 5 3 done talk linda@dragonwings
EXPLANATION
|
(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
|
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.
FORMATshift [n] |
(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
|
(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
|
(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
|
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.
FORMATbreak [n] |
EXPLANATION
|
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]
FORMATcontinue [n] |
EXPLANATION
|
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.
EXPLANATION
|
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
(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
|
(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
|
Piping the Output of a Loop to a UNIX Command. Output can be either piped to another command(s) or redirected to a file.
(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
|
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.
(The Script) #!/bin/sh 1 for person in bob jim joe sam do 2 mail $person < memo 3 done &
EXPLANATION
|
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.
(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
|
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.
(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
|
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.
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.
A function must be defined before it is used.
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.
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.
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.
To list functions and definitions, use the set command.
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.
If functions are stored in another file, they can be loaded into the current script with the dot command.
FORMATfunction_name () { commands ; commands; } |
dir () { echo "Directories: " ; ls -l | nawk '/^d/ {print $NF}' ; }
EXPLANATIONThe 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.
FORMATunset 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.
(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
|
(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
|
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.
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
|
(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
|
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.
FORMATtrap 'command; command' signal-number |
trap 'rm tmp*; exit 1' 1 2 15
EXPLANATIONWhen 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.
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.
trap 2
EXPLANATIONResets the default action for signal 2, SIGINT, which is used to kill a process, i.e., Control-C. |
trap 'trap 2' 2
EXPLANATIONSets 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.
trap " " 1 2
EXPLANATIONSignals 1 and 2 will be ignored by the shell. |
Listing Traps. To list all traps and the commands assigned to them, type trap.
(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
|
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.
(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
|
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.
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. |
(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
|
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.
(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
|
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.
(A Line from the Script Called "runit") while getopts :xyn: name
EXPLANATION
|
getopts Scripts. The following examples illustrate how getopts processes arguments.
(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
|
(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
|
(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
|
$ 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
|
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.
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
|
(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
|
When the shell is started using the sh command, it can take options to modify its behavior. See Table 8.17.
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. |
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.
1 $ set -f 2 $ echo * * 3 $ echo ?? ?? 4 $ set +f
EXPLANATION
|
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. |
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.
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. |
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: | 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: | 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:
|
4: | Perform the following:
|
1: | Write a script called greetme that will do the following:
|
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? |
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. |
1: | Write a script called nosy that will do the following:
|
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:
|
3: | Create a script called lookup that will do the following:
|
4: | Try the x and v options for debugging your script. How did you use these commands? How do they differ? |
1: | Write a script called checking that will do the following:
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: | 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: | 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
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. |
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.
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: | 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:
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:
|
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 |