xref: /original-bsd/bin/sh/USD.doc/t2 (revision c3e32dec)
%sccs.include.proprietary.roff%

@(#)t2 8.1 (Berkeley) 06/08/93

2.0 Shell procedures

The shell may be used to read and execute commands contained in a file. For example, sh file [ args \*(ZZ ] calls the shell to read commands from file. Such a file is called a command procedure or shell procedure. Arguments may be supplied with the call and are referred to in file using the positional parameters $1, $2, \*(ZZ\|. For example, if the file wg contains who \*(VT grep $1 then sh wg fred is equivalent to who \*(VT grep fred

UNIX files have three independent attributes, read, write and execute. The UNIX command chmod (1) may be used to make a file executable. For example, chmod +x wg will ensure that the file wg has execute status. Following this, the command wg fred is equivalent to sh wg fred This allows shell procedures and programs to be used interchangeably. In either case a new process is created to run the command.

As well as providing names for the positional parameters, the number of positional parameters in the call is available as $#\|. The name of the file being executed is available as $0\|.

A special shell parameter $\*(ST is used to substitute for all positional parameters except $0\|. A typical use of this is to provide some default arguments, as in, nroff -T450 -ms $\*(ST which simply prepends some arguments to those already given.

2.1 Control flow - for

A frequent use of shell procedures is to loop through the arguments ($1, $2, \*(ZZ) executing commands once for each argument. An example of such a procedure is tel that searches the file /usr/lib/telnos that contains lines of the form \*(ZZ fred mh0123 bert mh0789 \*(ZZ The text of tel is for i do grep $i /usr/lib/telnos; done The command tel fred prints those lines in /usr/lib/telnos that contain the string fred\|. tel fred bert prints those lines containing fred followed by those for bert.

The for loop notation is recognized by the shell and has the general form for name in w1 w2 \*(ZZ do command-list done A command-list is a sequence of one or more simple commands separated or terminated by a newline or semicolon. Furthermore, reserved words like do and done are only recognized following a newline or semicolon. name is a shell variable that is set to the words w1 w2 \*(ZZ in turn each time the command-list following do is executed. If in w1 w2 \*(ZZ is omitted then the loop is executed once for each positional parameter; that is, in $\*(ST is assumed.

Another example of the use of the for loop is the create command whose text is for i do >$i; done The command create alpha beta ensures that two empty files alpha and beta exist and are empty. The notation >file may be used on its own to create or clear the contents of a file. Notice also that a semicolon (or newline) is required before done.

2.2 Control flow - case

A multiple way branch is provided for by the case notation. For example, case $# in \*(Ca1) cat \*(AP$1 ;; \*(Ca2) cat \*(AP$2 <$1 ;; \*(Ca\*(ST) echo \\'usage: append [ from ] to\\' ;; esac is an append command. When called with one argument as append file $# is the string 1 and the standard input is copied onto the end of file using the cat command. append file1 file2 appends the contents of file1 onto file2. If the number of arguments supplied to append is other than 1 or 2 then a message is printed indicating proper usage.

The general form of the case command is case word in \*(Capattern\|) command-list\|;; \*(Ca\*(ZZ esac The shell attempts to match word with each pattern, in the order in which the patterns appear. If a match is found the associated command-list is executed and execution of the case is complete. Since \*(ST is the pattern that matches any string it can be used for the default case.

A word of caution: no check is made to ensure that only one pattern matches the case argument. The first match found defines the set of commands to be executed. In the example below the commands following the second \*(ST will never be executed. case $# in \*(Ca\*(ST) \*(ZZ ;; \*(Ca\*(ST) \*(ZZ ;; esac

Another example of the use of the case construction is to distinguish between different forms of an argument. The following example is a fragment of a cc command. for i do case $i in \*(DC-[ocs]) \*(ZZ ;; \*(DC-\*(ST) echo \\"unknown flag $i\\" ;; \*(DC\*(ST.c) /lib/c0 $i \*(ZZ ;; \*(DC\*(ST) echo \\"unexpected argument $i\\" ;; \*(DOesac done

To allow the same commands to be associated with more than one pattern the case command provides for alternative patterns separated by a \*(VT\|. For example, case $i in \*(Ca-x\*(VT-y) \*(ZZ esac is equivalent to case $i in \*(Ca-[xy]) \*(ZZ esac

The usual quoting conventions apply so that case $i in \*(Ca\\\\?) \*(ZZ will match the character ?\|.

2.3 Here documents

The shell procedure tel in section 2.1 uses the file /usr/lib/telnos to supply the data for grep. An alternative is to include this data within the shell procedure as a here document, as in, for i do grep $i \*(HE! \*(DO\*(ZZ \*(DOfred mh0123 \*(DObert mh0789 \*(DO\*(ZZ ! done In this example the shell takes the lines between \*(HE! and ! as the standard input for grep. The string ! is arbitrary, the document being terminated by a line that consists of the string following \*(HE\|.

Parameters are substituted in the document before it is made available to grep as illustrated by the following procedure called edg\|. ed $3 \*(HE% g/$1/s//$2/g w % The call edg string1 string2 file is then equivalent to the command ed file \*(HE% g/string1/s//string2/g w % and changes all occurrences of string1 in file to string2\|. Substitution can be prevented using \\ to quote the special character $ as in ed $3 \*(HE+ 1,\\\\$s/$1/$2/g w + (This version of edg is equivalent to the first except that ed will print a ? if there are no occurrences of the string $1\|.) Substitution within a here document may be prevented entirely by quoting the terminating string, for example, grep $i \*(HE\\\\# \*(ZZ # The document is presented without modification to grep. If parameter substitution is not required in a here document this latter form is more efficient.

2.4 Shell variables

The shell provides string-valued variables. Variable names begin with a letter and consist of letters, digits and underscores. Variables may be given values by writing, for example, user=fred box=m000 acct=mh0000 which assigns values to the variables user, box and acct. A variable may be set to the null string by saying, for example, null= The value of a variable is substituted by preceding its name with $\|; for example, echo $user will echo fred.

Variables may be used interactively to provide abbreviations for frequently used strings. For example, b=/usr/fred/bin mv pgm $b will move the file pgm from the current directory to the directory /usr/fred/bin\|. A more general notation is available for parameter (or variable) substitution, as in, echo ${user} which is equivalent to echo $user and is used when the parameter name is followed by a letter or digit. For example, tmp=/tmp/ps ps a >${tmp}a will direct the output of ps to the file /tmp/psa, whereas, ps a >$tmpa would cause the value of the variable tmpa to be substituted.

Except for $? the following are set initially by the shell. $? is set after executing each command.

$? 8
The exit status (return code) of the last command executed as a decimal string. Most commands return a zero exit status if they complete successfully, otherwise a non-zero exit status is returned. Testing the value of return codes is dealt with later under if and while commands.
$# 8
The number of positional parameters (in decimal). Used, for example, in the append command to check the number of parameters.
$$ 8
The process number of this shell (in decimal). Since process numbers are unique among all existing processes, this string is frequently used to generate unique temporary file names. For example, ps a >/tmp/ps$$ \*(ZZ rm /tmp/ps$$
$\|! 8
The process number of the last process run in the background (in decimal).
$- 8
The current shell flags, such as -x and -v\|.

Some variables have a special meaning to the shell and should be avoided for general use.

$\s-1MAIL\s0 8
When used interactively the shell looks at the file specified by this variable before it issues a prompt. If the specified file has been modified since it was last looked at the shell prints the message you have mail before prompting for the next command. This variable is typically set in the file .profile, in the user's login directory. For example, \s-1MAIL\s0=/usr/spool/mail/fred
$\s-1HOME\s0 8
The default argument for the cd command. The current directory is used to resolve file name references that do not begin with a /\|, and is changed using the cd command. For example, cd /usr/fred/bin makes the current directory /usr/fred/bin\|. cat wn will print on the terminal the file wn in this directory. The command cd with no argument is equivalent to cd $\s-1HOME\s0 This variable is also typically set in the the user's login profile.
$\s-1PATH\s0 8
A list of directories that contain commands (the search path\|). Each time a command is executed by the shell a list of directories is searched for an executable file. If $\s-1PATH\s0 is not set then the current directory, /bin, and /usr/bin are searched by default. Otherwise $\s-1PATH\s0 consists of directory names separated by :\|. For example, \s-1PATH\s0=:/usr/fred/bin:/bin:/usr/bin specifies that the current directory (the null string before the first :\|), /usr/fred/bin, /bin and /usr/bin are to be searched in that order. In this way individual users can have their own `private' commands that are accessible independently of the current directory. If the command name contains a / then this directory search is not used; a single attempt is made to execute the command.
$\s-1PS1\s0 8
The primary shell prompt string, by default, `$ '.
$\s-1PS2\s0 8
The shell prompt when further input is needed, by default, `> '.
$\s-1IFS\s0 8
The set of characters used by blank interpretation (see section 3.4).
2.5 The test command

The test command, although not part of the shell, is intended for use by shell programs. For example, test -f file returns zero exit status if file exists and non-zero exit status otherwise. In general test evaluates a predicate and returns the result as its exit status. Some of the more frequently used test arguments are given here, see test (1) for a complete specification. test s true if the argument s is not the null string test -f file true if file exists test -r file true if file is readable test -w file true if file is writable test -d file true if file is a directory

2.6 Control flow - while

The actions of the for loop and the case branch are determined by data available to the shell. A while or until loop and an if then else branch are also provided whose actions are determined by the exit status returned by commands. A while loop has the general form while command-list\*1 do command-list\*2 done

The value tested by the while command is the exit status of the last simple command following while. Each time round the loop command-list\*1 is executed; if a zero exit status is returned then command-list\*2 is executed; otherwise, the loop terminates. For example, while test $1 do \*(ZZ \*(DOshift done is equivalent to for i do \*(ZZ done shift is a shell command that renames the positional parameters $2, $3, \*(ZZ as $1, $2, \*(ZZ and loses $1\|.

Another kind of use for the while/until loop is to wait until some external event occurs and then run some commands. In an until loop the termination condition is reversed. For example, until test -f file do sleep 300; done commands will loop until file exists. Each time round the loop it waits for 5 minutes before trying again. (Presumably another process will eventually create the file.)

2.7 Control flow - if

Also available is a general conditional branch of the form, if command-list then command-list else command-list fi that tests the value returned by the last simple command following if.

The if command may be used in conjunction with the test command to test for the existence of a file as in if test -f file then process file else do something else fi

An example of the use of if, case and for constructions is given in section 2.10\|.

A multiple test if command of the form if \*(ZZ then \*(ZZ else if \*(ZZ then \*(ZZ else if \*(ZZ \*(ZZ fi fi fi may be written using an extension of the if notation as, if \*(ZZ then \*(ZZ elif \*(ZZ then \*(ZZ elif \*(ZZ \*(ZZ fi

The following example is the touch command which changes the `last modified' time for a list of files. The command may be used in conjunction with make (1) to force recompilation of a list of files. flag= for i do case $i in \*(DC-c) flag=N ;; \*(DC\*(ST) if test -f $i \*(DC then ln $i junk$$; rm junk$$ \*(DC elif test $flag \*(DC then echo file \\\\\'$i\\\\\' does not exist \*(DC else >$i \*(DC fi \*(DO esac done The -c flag is used in this command to force subsequent files to be created if they do not already exist. Otherwise, if the file does not exist, an error message is printed. The shell variable flag is set to some non-null string if the -c argument is encountered. The commands ln \*(ZZ; rm \*(ZZ make a link to the file and then remove it thus causing the last modified date to be updated.

The sequence if command1 then command2 fi may be written command1 && command2 Conversely, command1 \*(VT\*(VT command2 executes command2 only if command1 fails. In each case the value returned is that of the last simple command executed.

2.8 Command grouping

Commands may be grouped in two ways, { command-list ; } and ( command-list )

In the first command-list is simply executed. The second form executes command-list as a separate process. For example, (cd x; rm junk ) executes rm junk in the directory x without changing the current directory of the invoking shell.

The commands cd x; rm junk have the same effect but leave the invoking shell in the directory x.

2.9 Debugging shell procedures

The shell provides two tracing mechanisms to help when debugging shell procedures. The first is invoked within the procedure as set -v (v for verbose) and causes lines of the procedure to be printed as they are read. It is useful to help isolate syntax errors. It may be invoked without modifying the procedure by saying sh -v proc \*(ZZ where proc is the name of the shell procedure. This flag may be used in conjunction with the -n flag which prevents execution of subsequent commands. (Note that saying set -n at a terminal will render the terminal useless until an end-of-file is typed.)

The command set -x will produce an execution trace. Following parameter substitution each command is printed as it is executed. (Try these at the terminal to see what effect they have.) Both flags may be turned off by saying set - and the current setting of the shell flags is available as $-\|.

2.10 The man command

The following is the man command which is used to diplay sections of the UNIX manual on your terminal. It is called, for example, as man sh man -t ed man 2 fork In the first the manual section for sh is displayed.. Since no section is specified, section 1 is used. The second example will typeset (-t option) the manual section for ed. The last prints the fork manual page from section 2, which covers system calls. cd /usr/man : \'colon is the comment command\' : \'default is nroff ($N), section 1 ($s)\' N=n s=1 for i do case $i in \*(DC[1-9]\*(ST) s=$i ;; \*(DC-t) N=t ;; \*(DC-n) N=n ;; \*(DC-\*(ST) echo unknown flag \\\\\'$i\\\\\' ;; \*(DC\*(ST) if test -f man$s/$i.$s \*(DC then ${N}roff man0/${N}aa man$s/$i.$s \*(DC else : \'look through all manual sections\' \*(DC found=no \*(DC for j in 1 2 3 4 5 6 7 8 9 \*(DC do if test -f man$j/$i.$j \*(DC \*(DOthen man $j $i \*(DC \*(DO\*(THfound=yes \*(DC \*(DOfi \*(DC done \*(DC case $found in \*(DC \*(Cano) echo \\'$i: manual page not found\\' \*(DC esac \*(DC fi \*(DOesac done

Figure 1. A version of the man command