Writing a sh script is very similar to writing a csh script, since sh and csh support a common set of variables, and their expressions and control structures are semantically alike. However, since csh is your default login shell, you must specify the full path of sh at the top of the script in order to have sh to evaluate your script. To locate sh in your undergraduate account, execute the command whereis sh in the xterm. Like for csh scripts, you must use the chmod command to make your script executable before running it. Also, the print command echo and the pound sign # work in the same way for both types of shell.

This reading discusses the following features of sh:

Variables Predefined Variables

Like csh, sh also has environment and local variables. The environment variables $HOME, $PATH, $MAIL, $USER, $SHELL and $TERM that are predefined in csh are also predefined in sh. The local variables that are predefined in both csh and sh are:

Name Value
$0 The name of the shell script
$n n is a number ranges from 1 to 9. $n refers to the nth command-line argument
$* A list of all command-line arguments

In addition to this set of common local variables, sh has its own set of predefined local variables:

Name Value
$@ A list of all command-line arguments
$# The number of command-line arguments
$? The exit code of the last command

Every command executed in sh returns an exit status. If the execution of the command is successful, then a zero exit status is returned; otherwise, a nonzero exit status is returned. The variable $? returns the exit code of the command last executed.

Like in csh, the only difference between local and environment variables is that local variables are not inherited by subshells. However, if you wish to export a local variable from a parent shell to a subshell, the command export, which will be discussed below, allows you to do so.

Creating/Assigning a Variable

In sh, you do not need to use the set command to create and assign values to a new local variable. The syntax of variable assignment is:

name=value
where name is the variable that you want to create, and value is its value. By default, value should be one string only; if you want to assign more than one string to a variable, you have to enclose all strings in double quotes. Here is an example of variable creation and assignment:
$ firstName=Joe 
$ lastName=Smith 
$ age=19 
$ echo $firstname $lastname is $age 
Joe Smith is 19 

$ name=Joe Smith 
Smith: not found 

$ name="Joe Smith" 
$ echo $name 
Joe Smith 
Note that the dollar sign $ is used in sh to represent a prompt by default. Do not confuse this dollar sign with the dollar sign that is used to access variables, discussed in the next section.

Accessing a Variable

The two forms used in csh to access variables:

$name 
${name}
are used in the same way in sh to access variables. However, unlike csh, sh does not provide any method to access elements in a list. Therefore, you can create a list by enclosing all elements by double quotes as mentioned above, but you cannot modify the list or retrieve its elements.

Reading a Variable from Standard Input

In csh, a script reads in input from the user by the predefined variable $<. sh does not have a variable that does this task. To obtain input from the user in sh, you need to use the read command, which has the following form:

read { variable } 
where { variable } is a list of zero or more variables.

read reads one line from the user input and assigns each string from the line to the each variable in { variable }. Any strings that are left over are assigned to the last named variable. If you specify only one variable, then the entire line of input is stored as one value in that variable. Here is an example script that prompts the user for his or her full name:

#!/usr/bin/sh 
# scriptI.sh 
echo "Please enter your name: \c" 
read name 
echo Your name is $name 

$ scriptI.sh 
Please enter your name: Joe Paul Smith 
You name is Joe Paul Smith 
The following example illustrates what happens when you specify more than one variable:
#!/usr/bin/sh 
# scriptII.sh 
echo "Please enter your name: \c" 
read firstName lastName 
echo Your first name is $firstName 
echo Your last name is $lastName 

$ scriptII.sh 
Please enter your name: Joe Paul Smith 
Your first name is Joe 
Your last name is Paul Smith 

$ scriptII.sh 
Please enter your name: Smith 
Your first name is Smith 
Your last name is 
In the last execution of the script, the variable $lastName was not assigned any value because only one string was entered and that string was assigned to $firstName. The \c used in the first echo command of the previous example serves the same purpose as the -n option to echo: it prevents echo from printing a newline.

Exporting Variables

The export command allows you to mark local variables for export to the environment and works as follows:

export { variables }
where variables is a list of zero or more variables to be exported. If no variables are specified, then a list of all variables marked for export during the shell session is displayed.

To assign values to an environment variable, use the command env, which has the syntax:

env { variable=value } [command]
where variable=value is a list of zero or more variable assignments, and command is an optional list of commands. env assigns values to specified environment variables and then executes command if provided. If no variable or command is specified, then a list of current environments is displayed.

The following example creates a local variable called DATABASE and marks it for export. A subshell is then created and you will see that it also gets a copy of DATABASE:

$ DATABASE=/dbase/db 
$ export DATABASE 
$ sh 
$ echo $DATABASE 
/dbase/db 
$ env DATABASE=/dbase/db/logfile 
$ echo $DATABASE 
/dbase/db/logfile 
When sh is executed, a subshell is invoked and since the variable DATABASE is exported, the subshell also contains a copy of it.

Expressions

sh supports two types of expressions: arithmetic and conditional. These two types of expressions use the expr and test commands respectively.

Arithmetic Expressions

Arithmetic expressions are evaluated using the expr command, which has the syntax:

expr expression
expression may be constructed by applying the following binary operators to integer operands, grouped in decreasing order of precedence:

Operator Meaning
* / % Multiplication, division, remainder
+ - Addition, subtraction
= > >= < <= != Comparison operators
& Logical AND
| Logical OR

expression may be constructed using the string operator:

Operator Meaning
substr string start length Returns the substring of string starting from index start and having length characters
index string charList Returns the index of the first character in string that appears in charList. The first character has position 1.
length string Returns the length of string.

All components in expression must be separated by spaces, and all characters that have special meaning to the shell (e.g., the wildcard *) must be escaped by a backslash \. expression may yield a numeric or string result, depending on the operators that it contains. The result of expression may be assigned to a variable using command substitution by enclosing expression in a pair of grave accents `expression`. Parentheses may be used to explicitly control the order of evaluation, but they must be escaped using the backslash as well. The following example illustrates some of the functions of expr and makes plentiful use of command substitution:

$ x=1 
$ x=`expr $x + 1` 
$ echo $x 
2 
$ x=`expr 2 + 3 \* 5` 
$ echo $x 
17 
$ echo `expr \( 2 + 2 \) \* 5` 
20 
$ echo `expr length "cat"` 
3 
$ echo `expr substr "donkey" 4 3` 
key 
$ echo `expr \( 4 \> 5 \)` 
0 
$ echo `expr \( 4 \> 5 \) \| \( 6 \< 7 \)` 
1 
When a pair of grave accents surrounds a command, this command is replaced by its standard output after execution. This type of substitution is called command substitution and is supported by all types of shell. For example, the output of the command `expr $x + 1` in the first example is replaced by the string 2, which is then assigned to the variable x.

The wildcard character * and the redirection operators < > that are used in csh can also be used in sh in the same way. In fact, all wildcards, redirection and piping characters supported by csh are supported by all types of shell. In addition, sh supports several more redirecting features such error redirection discussed in the section Redirecting Error.

The last two examples return the result of comparisons that use operators < and >. Like in csh, sh uses a zero to represent false and a one to represent true.

Conditional Expressions

sh supports a group of expressions, which are called logical expressions, that evaluate to true or false. To use these expressions, you need the test command:

test expression
where expression may take the following forms:

Form Meaning
-d filename True if filename is a directory
-f filename True if filename is a file
-r filename True if filename is readable
-w filename True if filename is writable
-x filename True if filename is executable
-l string True if length of string is nonzero
-n string True if string contains at least one character
str1 = str2 True if str1 is equal to str2
str1 != str2 True if str1 is not equal to str2
int1 -eq int2 True if integer int1 is equal to integer int2
int1 -ne int2 True if integer int1 is not equal to integer int2
int1 -gt int2 True if integer int1 is greater than integer int2
int1 -ge int2 True if integer int1 is greater than or equal to integer int2
int1 -lt int2 True if integer int1 is less than integer int2
int1 -le int2 True if integer int1 is less than or equal to integer int2

test is very picky about the syntax of expression. Your syntax must be exactly like the syntax listed above (be careful with the spaces!). test supports many options and the table above just listed a few of them; you can view the test man page to learn about that options that are not discussed here.

test returns a zero exit code if expression evaluates to true; otherwise, it returns a nonzero exit code. test expressions are often used in sh control structures .

Control Structures

Like csh, sh supports a wide range of control structures that make it a high-level programming tool. The next subsections describe these control structures in alphabetical order.

case...in...esac

The case command supports multiway branching based on the value of a single string; it works like the switch command in csh. The syntax of case is:

case expression in 
    pattern { | pattern }) 
    list 
    ;; 
esac 
where expression is an expression that evaluates to a string, pattern { | pattern} is a list of one or more patterns, with a logical OR | to separate each of them. These patterns may also include wildcards. list is a list of one or more commands.

You may include as many pattern/list associations as you wish. sh evaluates expression and then compares the result to each pattern { | pattern } in turn, from top to bottom. When the first matching pattern is found, its associated list of commands is executed and then sh skips to the matching esac. If no match is found and a default pattern is supplied (designated by an asterisk *), then the list following the default pattern is executed. If no match is found and no default pattern is given, then sh skips to the matching esac.

The pair of semicolons is used to separate consecutive pattern/list associations. Do not forget to put a single parenthesis after every pattern { | pattern }. Here is an example that uses case:

#!/bin/usr/sh 
# scriptIII.sh 
echo "Please enter a color: \c" 
read color 
case $color in 
    "red") 
    echo You have entered red 
    ;; 
    "blue") 
    echo You have entered blue 
    ;; 
    "yellow") 
    echo You have entered yellow 
    ;; 
    *) 
    echo You have entered other color 
    ;; 
esac 

$ scriptIII.sh 
Please enter a color: red 
You have entered red 

$ scriptIII.sh 
 Please enter a color: purple 
You have entered other color 
Like the command switch, case is also used frequently in a while loop. The section while...done has an example that illustrates this combination.

for...do...done

The for command allows a list of commands to be executed several times, using a different value of the loop variable during each iteration. Here is its syntax:

for name in { word } 
do 
    list 
done 
where { word } contains one or more strings, and list is a list of commands.

The for command loops the value of the variable name through each word, evaluating the commands in list after each iteration. If { word } is not supplied, then the command-line arguments are used. The break command causes the loop to end immediately, and the continue command causes the loop to jump immediately to the next iteration. Here is an example that uses the for control structure:

#!/bin/usr/sh 
# scriptIV.sh 
for color in red yellow green blue 
do 
    echo one color is $color 
done 

$ scriptIV.sh 
one color is red 
one color is yellow 
one color is green 
one color is blue 
Note that arguments contained in word are not enclosed in double quotes, unlike the csh for command.

if...then...fi

The if command supports nested conditional branches and has the following syntax:

if list1 
then 
    list2 
elif list3 
then 
    list4 
else 
    list5 
fi 
The if command works exactly like the C++ if statement. The elif and else parts are both optional. However, the elif part can be repeated several times, and the else part can occur only once. The following script illustrates the use of if:
#!/bin/usr/sh 
# scriptV.sh 
echo "enter a number: \c" 
read number 
if test $number -lt 0 
then 
    echo negative 
elif test $number -eq 0 
then 
    echo zero 
else 
    echo positive 
fi 

$ scriptV.sh 
enter a number: 1 
positive 

$ scriptV.sh 
enter a number: -1 
negative 

while...done

The while command executes one series of command as long as another series of command succeeds. It has the following syntax:

while list1 
do 
    list2 
done 
The while command executes the commands in list2 repeatedly, as long as the last command in list1 returns a zero exit status. If list2 is empty, then the do keyword should be omitted. The break command causes the loop to end immediately, and the continue command causes the loop to immediately jump to the next iteration.

Here is an example script that uses the while control structure. Study it carefully and try to figure out what the script does:

#!/bin/usr/sh 
# scriptVI.sh 
if test "$1" -eq " " 
then 
    echo "Usage: integer" 
    exit 
fi 

x=1 #set outer-loop value 
while test $x -le $1 #outer loop 
do 
    y=1 #set inner-loop value 
    while test $y -le $1 
    do #generate one table entry 
        echo `expr $x \* $y` " \c" 
        y=`expr $y + 1` #update inner-loop count 
    done 
    echo #display from a newline 
    x=`expr $x + 1` #update outer-loop count 
done 

$ scriptVI.sh 5 
1 2 3 4 5 
2 4 6 8 10 
3 6 9 12 15 
4 8 12 16 20 
5 10 15 20 25 
This sample script reads in a command-line argument and generates a multiplication table for that argument. Several command substitutions take place in scriptVI.sh, and a pair of grave accents is used to surround all these commands. Also, double quotes are used in a few places to inhibit wildcard substitutions.

The following example contains a case control structure nested in a while loop:

#! /bin/sh
# menu.sh

echo "Menu Test Program"
stop=0

while test $stop -eq 0
do
    echo " "

    cat << EndOfMenu
    1    : Print the Date
    2    : Print the CWD
    3    : Exit
EndOfMenu
    
    echo
    echo -n "Please enter your choice: "
    read reply
    echo
    
    case $reply in
	"1")
	date
	;;
	
	"2")
	pwd
	;;

	"3")
	stop=1
	;;

	*)
	echo illegal choice
	;;
    esac

done


$ menu.sh
Menu Test Program
 
    1    : Print the Date
    2    : Print the CWD
    3    : Exit
 
Please enter your choice: 1
 
Fri Jun  1 14:37:04 PDT 2001
 
    1    : Print the Date
    2    : Print the CWD
    3    : Exit
 
Please enter your choice: 2
 
/cs219/unix/examples
 
    1    : Print the Date
    2    : Print the CWD
    3    : Exit
 
Please enter your choice: 4
 
illegal choice
 
    1    : Print the Date
    2    : Print the CWD
    3    : Exit
 
Please enter your choice: 3
 
$
This script works exactly like scriptX.csh in the csh control structures section. Look at scriptX.csh again and consider the similarities and differences between the syntax of the these two languages.

Redirecting Error

When you misuse a command, or make a typo in a command, Unix returns an error message. By default, Unix redirects any error message to the standard error channel (i.e., the screen). But if you want to redirect the error message to a file, you can only do so in sh. sh allows you to do error channeling by using a file descriptor and redirection characters. A file descriptor is an integer that a program uses to refer to a file. There are several descriptors predefined in sh, and the following describes the three most common and what they are for:

To redirect standard error output, you need to type the file descriptor 2 in front of a redirection character and a filename after the character. For example, the man command always writes a couple of lines to the standard error channel. It writes Reformatting page, Wait when it begins and done when it ends. To redirect this error message to a file, you would type something like:

$ man ls > ls.txt 2> err.txt 
$ cat err.txt 
Reformatting page. Wait...done 

In this example, the ls man page is written to the file ls.txt, and the message Reformatting page. Wait...done is redirected to the file err.txt.