Previous Section  < Day Day Up >  Next Section

8.5. Conditional Constructs and Flow Control

Conditional commands allow you to perform some task(s) based on whether a condition succeeds or fails. The if command is the simplest form of decision making; the if/else command allows a two-way decision; and the if/elif/else command allows 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.4.

Table 8.4. String, Integer, and File Testing

Test Operator

Test For

String Test

string1 = string2

String1 is equal to String2 (space surrounding = required)

string1 != string2

String1 is not equal to String2 (space surrounding != required)

string

String is not null

–z string

Length of string is zero

–n string

Length of string is nonzero

 

EXAMPLE


test -n $word      or      [ -n $word ]

test tom = sue      or      [ tom = sue ]


Integer Test

int1 –eq int2

Int1 is equal to int2

int1 –ne int2

Int1 is not equal to int2

int1 –gt int2

Int1 is greater than int2

int1 –ge int2

Int1 is greater than or equal to int2

int1 –lt int2

Int1 is less than int2

int1 –le int2

Int1 is less than or equal to int2

Logical Test

expr1 -a expr2

Logical AND

expr1 -o expr2

Logical OR

! expr

Logical NOT

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 writable

–x filename

File is executable


8.5.1 Testing Exit Status: The test Command

The following examples illustrate how the exit status is tested.

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. (Refer back to Table 8.4.)

Example 8.11.

(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 by the test command

9   $ echo  $?

    1               Failure


EXPLANATION

  1. The variable name is assigned the string Tom.

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

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

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

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

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

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

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

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

8.5.2 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. Typically, 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 0 exit status; if not, it returns 1. 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.[2]

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

FORMAT


if command

then

     command

     command

fi

---------------------------------

if test expression

then

     command

fi



         or



if [ expression ]

then

     command

fi

-------------------------------


Example 8.12.

1   if ypmatch "$name" passwd > /dev/null 2>&1

2   then

         echo Found $name!

3   fi


EXPLANATION

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

    
    if grep "$name" /etc/passwd > /dev/null 2>&1
    
    

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

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

Example 8.13.

1   echo  "Are you okay (y/n) ?"

    read answer

2   if [ "$answer" = Y -o "$answer" = y  ]

    then

         echo  "Glad to hear it."

3   fi


EXPLANATION

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

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

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

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

8.5.3 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 0 as an argument, the program exited with success. A nonzero argument indicates some kind of failure. The argument given to the exit command is stored in the shell's ? variable.

Example 8.14.

(The Script)

     # Name: bigfiles

     # Purpose: Use the find command to find any files in the root

     # partition that have not been modified within the past n (any

     # number within 30 days) days and are larger than 20 blocks

     # (512-byte blocks)



1    if  [ $# -ne 2 ]

     then

         echo  "Usage: $0 mdays size " 1>&2

         exit 1

2    fi

3    if  [ $1 -lt 0 -o $1 -gt 30 ]

     then

         echo "mdays is out of range"

         exit 2

4    fi

5    if [ $2 -le 20 ]

     then

         echo "size is out of range"

         exit 3

6    fi

7    find / -xdev -mtime $1 -size +$2 -print



(The Command Line)

$ bigfiles

Usage: bigfiles mdays size

$ echo $?

1

$ bigfiles 400 80

mdays is out of range

$ echo $?

2

$ bigfiles 25 2

size is out of range

$ echo $?

3

$ bigfiles 2 25

(Output of find prints here)


EXPLANATION

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

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

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

  4. The fi ends the if block.

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

  6. The fi ends the if block.

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

8.5.4 Checking for Null Values

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

Example 8.15.

(The Script)

1    if [ "$name" = "" ]    # Alternative to  [ ! "$name" ]  or  [ -z "$name" ]

     then

         echo The name variable is null

     fi



(From System showmount program, which displays all remotely mounted systems)

     remotes=`/usr/sbin/showmount`

2    if [ "X${remotes}" != "X" ]

     then

         /usr/sbin/wall ${remotes}

                      ...

3    fi


EXPLANATION

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

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

  3. The fi terminates the if.

8.5.5 The if/else Command

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

FORMAT


if  command

then

    command(s)

else

    command(s)

fi


Example 8.16.

(The Script)

     #!/bin/sh

1    if ypmatch "$name" passwd > /dev/null 2>&1[a]

2    then

         echo Found $name!

3    else

4        echo  "Can't find $name."

         exit 1

5    fi


EXPLANATION

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

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

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

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

  5. The fi terminates the if.

Example 8.17.

(The Script)

#!/bin/sh

# Scriptname: idcheck

# purpose: check user ID to see if user is root.

# Only root has a uid of 0.

# Format for id output:uid=9496(ellie) gid=40 groups=40

# root's uid=0



1    id=`id | nawk –F'[=(]'  '{print $2}'`     # Get user ID

     echo your user id is: $id

2    if [ $id –eq 0 ]

     then

3        echo "you are superuser."

4    else

         echo "you are not superuser."

5    fi



     (The Command Line)

6    $ idcheck

     your user id is: 9496

     you are not superuser.

7    $ su

     Password:

8    # idcheck

     your user id is: 0

     you are superuser


EXPLANATION

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

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

  3. If id is not equal to 0, the else statement is executed.

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

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

  6. The su command switches the user to root.

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

8.5.6 The if/elif/else Command

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

FORMAT


if  command

then

    command(s)

elif command

then

    commands(s)

elif command

then

    command(s)

else

    command(s)

fi


Example 8.18.

(The Script)

   #!/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 13 -a $age -lt  20 ]

    then

        echo "Rebel without a cause"

    elif [ $age  -ge 20 -a  $age -lt  30 ]

    then

        echo "You got the world by the tail!!"

    elif [ $age -ge  30 -a  $age -lt 40 ]

    then

        echo "Thirty something..."

4   else

        echo "Sorry I asked"

5   fi



(The Output)

$ tellme

How old are you? 200

Welcome to our planet!

$ tellme

How old are you? 13

Rebel without a cause

$ tellme

How old are you? 55

Sorry I asked


EXPLANATION

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

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

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

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

  5. The fi terminates the initial if statement.

8.5.7 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.4 on page 333.) You will find file testing a necessary part of writing dependable scripts.

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

Example 8.19.

(The Script)

    #!/bin/sh

    file=./testing


EXPLANATION

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

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

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

  4. The fi terminates the innermost if command.

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

  6. This fi goes with the first if.

8.5.8 The null Command

The null command, represented by a colon, is a built-in, do-nothing command that returns an exit status of 0. 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 a looping command to make the loop a forever loop.

Example 8.20.

(The Script)

1    name=Tom

2    if grep "$name" databasefile > /dev/null 2>&1

     then

3        :

4    else

         echo  "$1 not found in databasefile"

         exit 1

     fi


EXPLANATION

  1. The variable name is assigned the string Tom.

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

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

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

Example 8.21.

(The Command Line)

1   $ DATAFILE=

2   $ : ${DATAFILE:=$HOME/db/datafile}

    $ echo $DATAFILE

    /home/jody/ellie/db/datafile

3   $ : ${DATAFILE:=$HOME/junk}

    $ echo $DATAFILE

    /home/jody/ellie/db/datafile


EXPLANATION

  1. The variable DATAFILE is assigned null.

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

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

Example 8.22.

(The Script)

    #!/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." 1782

        exit 1

5    fi


EXPLANATION

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

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

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

  4. If the expr command fails, it returns a nonzero exit status, the echo command displays the message to standard error (1782), and the program exits.

  5. The fi ends the if block.

8.5.9 The case Command

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

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

FORMAT


case variable in

value1)

    command(s)

    ;;

value2)

    command(s)

    ;;

*)

command(s)

    ;;

esac


Example 8.23.

(The Script)

    #!/bin/sh

    # Scriptname: colors



1   echo -n "Which color do you like?"

    read color

2   case "$color" in

3   [Bb]l??)

4       echo I feel $color

        echo The sky is $color

5       ;;

6   [Gg]ree*)

        echo  $color is for trees

        echo  $color is for seasick;;

7   red | orange)             # The vertical bar means "OR"

        echo  $color is very warm!;;

8   *)

        echo  No such color as $color;;

9   esac

10  echo  "Out of case"


EXPLANATION

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

  2. The case command evaluates the expression $color.

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

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

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

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

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

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

  9. The esac statement terminates the case command.

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

8.5.10 Creating Menus with the here document and case Command

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

Example 8.24.

(The .profile file)

    echo "Select a terminal type: "

1   cat << ENDIT

        1) vt 120

        2) wyse50

        3) sun

2   ENDIT

3   read choice

4   case "$choice" in

5   1)   TERM=vt120

         export TERM

         ;;

    2)  TERM=wyse50

        export TERM

        ;;

6   3)   TERM=sun

       export TERM

        ;;

7   esac

8   echo "TERM is $TERM."



(The Output)

$ . .profile

Select a terminal type:

1) vt120

2) wyse50

3) sun

3             <-- User input

TERM is sun.


EXPLANATION

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

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

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

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

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

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

  7. The esac terminates the case command.

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

    Previous Section  < Day Day Up >  Next Section