Previous Section  < Day Day Up >  Next Section

12.5. Conditional Constructs and Flow Control

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

The Korn 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 [ and the [[ symbols. The Bourne shell encloses an expression in a set of single brackets: [ and ]. The Korn shell has a more sophisticated method for testing expressions. The expression is enclosed in double brackets: [[ and ]]. In the single brackets, the expansion of wildcards is not allowed; with the double brackets, wildcard expansion is supported and a new set of operators has been added. The result of a command is tested, with zero status indicating success, and nonzero status indicating failure.

12.5.1 Testing Exit Status and the $? Variable

The ? variable contains a number value (between 0 and 255) representing the exit status of the last command that exited. If the exit status is zero, the command exited with success; if nonzero, the command failed in some way. You can test the exit status of commands and use the test command to test the exit status of expressions.

The following examples illustrate how the exit status is tested. The single brackets are used in the Bourne shell, and although perfectly acceptable in the Korn shell, Dr. Korn provides you with the new double-bracket notation for testing expressions.

Example 12.14.

   (The Command Line)

1   $ name=Tom

2   $ grep "$name" datafile

    Tom Savage:408-124-2345

3   $ print $?

    0                         # Success

4   $ test $name = Tom

5   $ print $?

    0                         # Success

6   $ test $name != Tom

    $ print $?

    1                         # Failure

7   $ [ $name = Tom ]         # Brackets instead of the test command

8   $ print $?

    0

9   $ [[ $name = [Tt]?m ]]    # New ksh test command

10  $ print $?

    0


EXPLANATION

  1. The string Tom is assigned to the variable name.

  2. The grep command will search for string Tom in datafile, and if successful in its search, will display the line found.

  3. The ? variable, accessed by $?, 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 and numbers, and to perform file testing. It returns an exit status of 0 if the expression is true, and an exit status of 1 if the expression fails. There must be spaces surrounding the equal sign.

  5. The value of name is tested to see if it is equal to Tom. The test command returns an exit status of 0, meaning that $name does evaluate to Tom.

  6. The value of name is tested to see if it is equal to Tom. The test command returns an exit status of 1, meaning that name is not equal to Tom.

  7. 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.

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

  9. The new Korn shell test command, [[, is used. The new test allows shell metacharacter expansion. If the variable matches Tom, tom, Tim, tim, and so on, the test will return a successful status, 0.

  10. The variable name did match a string beginning with T or t and ending in m, resulting in a successful exit status ($?) of 0.

12.5.2 The Old test Command

The test command is used to evaluate conditional expressions, returning true or false. It returns zero exit status for true, and nonzero exit status for false. Either the test command or the brackets can be used. The Korn shell introduced a new way of testing expressions with double brackets. For backward compatibility with the Bourne shell, the older form of test can be used with either the test command or the single brackets. However, the preferred method for Korn shell programmers is the new test with double brackets. A complete list of test operators (both old and new style) appear in Table 12.6.

Table 12.6. Testing and Logical Operators

Test/Operator

Tests For

String Testing

string1 = string2

string1 is equal to string2

string1 != string2

string1 is not equal to string2

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 Testing (Old-Style role="strong">test Used with Bourne Shell)

int1 –eq int2

int1 is equal to int2

int1 –ge int2

int1 is greater than or equal to int2

int1 –gt int2

int1 is greater than int2

int1 –le int2

int1 is less than or equal to int2

int1 –lt int2

int1 is less than int2

int1 –ne int2

int1 is not equal to int2

Logical Operators (Old-Style test)

!

NOT operator

–a

AND operator

–o

OR operator

File Testing (Old-Style test)

–b filename

Block special file

–c filename

Character special file

–d filename

Directory existence

–f filename

File existence and not a directory

–g filename

Set-group-ID is set

–h filename

Symbolic link

–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


12.5.3 The New test Command

With the [[ . . . ]] compound test command, additional operators are available. Wildcards can be used in string-matching tests, and many of the errors from the old test have been eliminated. New string test operators are listed in Table 12.7.

Example 12.15.

(The Script)

    read answer

1   if [[ $answer = [Yy]* ]]     # Test for Yes or yes or Y or y, etc.

    then...



    Example:

    (The Script)

    guess=Noone

2   if [[ $guess != [Nn]o@(one|body) ]]    # Test for Noone, noone, or Nobody, nobody...

    then. . .



    Example:

    (The Command Line)

3   [[ apples < oranges ]]

    print $?

    0

4   [[ apples > oranges ]]

    print $?

    1

5   $ name="Joe Shmoe"

    $ [ $name = "Abe Lincoln" ]              # old style

    ksh: Shmoe: unknown test operator

6   $ [[ $name = "Abe Lincoln" ]]            # new style

    $ echo $?

    1


Table 12.7. String Testing (New-Style Test)

String Testing Operator

Tests For

string = pattern

string matches pattern[a]

string != pattern

string does not match pattern

string1 < string2

ASCII value of string1 is less than string2

string1 > string2

ASCII value of string1 is greater than string2

–n string

string is nonzero in length, nonnull parameter

–z string

string is zero in length, null parameter


[a] On versions newer than 1988, the == operator is permitted.

EXPLANATION

  1. The answer read in from the user is tested to see if it matches anything starting with Y or y.

  2. The variable guess is tested. If it is not equal to a string starting with N or n, followed by an o, and exactly one or body (for example, noone or nobody ) the then command would be executed.

  3. The string apples is tested to see if it comes before oranges in the ASCII collating sequence. It does.

  4. The string apples is tested to see if it comes after oranges in the ASCII collating sequences. It does not.

  5. In the old-style test, the variable name is split into separate words. Because the = operator expects a single string as its left operand, the test command fails. To fix the problem, the variable should be enclosed in double quotes.

  6. In the new-style test, the variable is not split up into separate words; therefore, double quotes are not required around $name.

12.5.4 File Testing with Binary Operators

The binary operators for testing files require two operands (i.e., a file on either side of the operator). See Table 12.8 for a list of binary file-testing operators.

Table 12.8. Binary File Testing and Logical Operators

Operator

Tests For

file1 –nt file2

True if file1 is newer than file2

file1 –ot file2

True if file1 is older than file2

file1 –ef file2

True if file1 is another name for file2


12.5.5 Expression Testing with Logical Operators

The Korn shell, like C, provides logical testing of the truth or falsity of expressions. They are listed in Table 12.9.

Table 12.9. Logical Operators

Operator

Tests For

&&

The AND operator evaluates the expression on the left-hand side of &&; if true, the expression on the right side of && is tested and must also be true. If one expression is false, the expression is false. The && operator replaces –a; for example, (( ( $x && $y ) > 5 )).

||

The OR operator evaluates the expression on the left-hand side of the || operator; if true, the expression is true; if false, the expression on the right-hand side of the || is evaluated; if true, the expression is true. Only if both expressions are false will the expression evaluate to false. The || operator replaces –o; for example, (( ( $x || $y ).


12.5.6 File Testing with Flags

The Korn shell provides a number of built-in test commands for checking the attributes of files, such as existence, type, permissions, and so forth. The file-testing options (also called flags) are listed in Table 12.10.

Example 12.16.

   (The Script)

1  file=/etc/passwd

2  if [[ -f $file && (–r $file || –w $file) ]]

   then

3    print $file is a plain file and is either readable or writable

   fi


Table 12.10. File Testing (New test Flags)

Flag

Tests For

Korn Shell Only

–a file

file exists

–e file

file exists (versions newer than 1988)

–L file

file exists and is a symbolic link

–O file

You are the owner of file

–G file

Your group ID is the same as file's

–S file

file exists and is a socket

Bourne and Korn Shells

–b file

file exists and is a block special file

–c file

file exists and is a character special file

–d file

file exists and is a directory

–f file

file exists and is not a directory

–g file

file exists and is setgid

–k file

file exists and sticky bit is set

–p file

file exists and is a named pipe

–r file

file exists and is readable

–s file

file has a nonzero size

–u file

file exists and is setuid

–w fle

file exists and is writable

–x file

file exists and is executable


EXPLANATION

  1. The variable file is assigned /etc/passwd.

  2. The file test operators test if the file is a plain file and is either readable or writable. The parentheses are used for grouping. In the old test, the parentheses had to be escaped with a backslash.

  3. If both of the tests are true, the file is a plain file, and it is either readable or writable, this line is executed.

12.5.7 The if Command

The simplest form of conditional is the if command. The command following the if keyword is executed and its exit status is returned. If the exit status is 0, the command succeeded and the statement(s) after the then keyword are executed.

In the C shell and C language, the expression following the if command is a Boolean-type expression. But in the Bourne and Korn shells, the statement following the if is a command or group of commands. The exit status of the last command of the if line is used to determine whether to continue and execute commands under the then statement. If the exit status of the last command on the if line is 0, the commands under the then statement are executed. The fi terminates the command list to be executed after the then. If the exit status is nonzero, meaning that the command failed in some way, the statement(s) after the then statement are ignored and control goes to the line directly after the fi statement.

Conditional commands can be nested. Every if must have a corresponding fi. The fi is paired with the closest if. Using indentation to format your if blocks helps when debugging your programs.

FORMAT


if command

then     # Testing command exit status

    command

    command

fi

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

if test expression

then     # Using the test command to test expressions

    command

fi





            or



if [ expression ]

then                  # Using the old-style test command--

    command           # brackets replace the word test

fi

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

if [[ expression ]]

then                  # New-style brackets for testing expressions

    command

fi

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





if command

then

...

    if command

    then

    ...

        if command         # Nested conditionals

        then

        ...

        fi

    fi

fi




Example 12.17.

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

2   then

         echo Found $name!

3   fi


EXPLANATION

  1. The ypmatch command is an NIS 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.

  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.

12.5.8 Using the Old-Style Bourne test

If you have been programming in the Bourne shell, the Korn shell is backward-compatible, allowing your Bourne shell scripts to be executed properly by the Korn shell. Many Bourne shell programmers, when converting to Korn shell, still use the old-style test command when evaluating expressions. If you are reading or maintaining scripts, you may find the old syntax alive and well. Therefore, a brief discussion of the old syntax may help you, even if you are writing your own scripts with the new Korn shell test command.

Example 12.18.

    #!/bin/ksh

    # Scriptname: are_you_ok

1   print "Are you ok (y/n) ?"

    read answer

2   if [ "$answer" = Y -o "$answer" = y ]     # Old-style test

    then

        print "Glad to hear it."

3   fi


EXPLANATION

  1. The user is asked the question, Are you ok (y/n) ?. The read command causes the program to wait for user input.

  2. The test command, represented by a [, is used to test expressions and returns an exit status of 0 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.)

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

12.5.9 Using the New-Style Korn test

The new-style Korn shell testing allows expressions to contain shell metacharacters and Korn shell operators such as && and ||.

Example 12.19.

   #!/bin/ksh

   # Scriptname: are_you_ok2

1   print "Are you ok (y/n) ?"

    read answer

2   if [[ "$answer" = [Yy]* ]]          # New-style test

    then

         print "Glad to hear it."

3   fi


EXPLANATION

  1. The user is asked the question, Are you ok (y/n) ?. The read command causes the program to wait for user input.

  2. The [[ ]] is a special Korn shell construct used to test expressions. If the answer evaluates to Y or y followed by any number of characters, the commands after the then statement are executed.

  3. The fi statement terminates the if.

12.5.10 Using the Old-Style Bourne test with Numbers

To test numeric expressions, the old-style Bourne shell test command and its operators are still acceptable in the Korn shell, but the new-style let command is preferred.

Example 12.20.

1    if [ $# -lt 1 ]

     then

         print "$0: Insufficient arguments " 1>&2

         exit 1

2   fi


EXPLANATION

  1. The statement reads: If the number of arguments is less than 1, print the error message and send it to standard error. Then exit the script. The old style of testing integers is used with the test command.

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

12.5.11 The let Command and Testing Numbers

Although it is still acceptable to use single square brackets and old-style Bourne shell numeric operators for testing numeric expressions, the preferred Korn shell method is to use the double parentheses and the new C language–style numeric operators when testing numeric expressions. Note that the double brackets are only used for testing string expressions and for file tests (see Table 12.10).

Example 12.21.

1    if (( $# < 1 ))

     then

         print "$0: Insufficient arguments " 1>&2

         exit 1

2   fi


EXPLANATION

  1. The statement reads: If the number of arguments is less than 1, print the error message and send it to standard error. Then exit the script. This is the preferred way to perform numeric tests in the Korn shell.

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

12.5.12 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 12.22.

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

2   then

         print Found $name!

3   else

4        print "Can't find $name."

         exit 1

5   fi


EXPLANATION

  1. The ypmatch command searches for its argument, $name, in the NIS passwd database. 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. The print function sends output to the screen and the program exits.

  5. This marks the end of the if construct.

12.5.13 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

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

if [[ string expression ]]      or       if (( numeric expression ))

then

    command(s)

elif [[ string expression  ]]   or       elif (( numeric expression ))

then

    commands(s)

elif [[ string expression ]]    or       elif(( numeric expression ))

then

    command(s)

else

    command(s)

fi


Example 12.23.

(The Script)

   #!/bin/ksh

   # Scriptname: tellme

1   read age?"How old are you? "

2   if (( age < 0 || age > 120 ))

    then

         print "Welcome to our planet! "

         exit 1

    fi

3   if (( age >= 0 && age < 13 ))

    then

         print "A child is a garden of verses"

    elif (( age > 12 && age < 20 ))

    then

         print "Rebel without a cause"

    elif (( age >= 20 && age < 30 ))

    then

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

    elif (( age >= 30 && age < 40 ))

    then

         print "Thirty something..."

4   else

         print "Sorry I asked"

5   fi



(The Output)

     $ tellme

1    How old are you? 200

2    Welcome to our planet!



     $ tellme

1    How old are you? 13

3    Rebel without a cause



     $ tellme

1    How old are you? 55

4    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 double parentheses. If age is less than 0 or greater than 120, the print command is executed and the program terminates with an exit status of 1. The interactive shell prompt will appear. Note that the dollar sign ($) is not required to perform variable substitution when using the (( )) operators.

  3. A numeric test is performed within the double parentheses. If age is greater than 0 and less than 13, the let command returns exit status 0, true.

  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.

12.5.14 The exit Command

The exit command is used to terminate the script and get back to the command line. You may want the script to exit if some condition does not test true. The argument to the exit command is an integer, ranging from 0 to 255. When the program exits, the exit number is stored in the shell's ? variable.

Example 12.24.

(The Script)

     #!/bin/ksh

     # Scriptname: filecheck

     # Purpose: Check to see if a file exists, what type it is, and its permissions.



1   file=$1      # Variable is set to first command-line argument

2   if [[ ! -a $file ]]

    then

         print "$file does not exist"

         exit 1

    fi

3   if [[ -d $file ]]

    then

         print "$file is a directory"

4   elif [[ -f $file ]]

    then

5      if [[ -r $file && -w $file && -x $file ]]

       then

           print "You have read, write, and execute permission on

           file $file"

       else

6          print "You don't have the correct permissions"

           exit 2

       fi

   else

7      print "$file is neither a file nor a directory. "

       exit 3

8   fi



(The Command Line)

9   $ filecheck testing

    testing does not exist

10  $ echo $?

    1


EXPLANATION

  1. The first command-line argument passed to this program ($1) is assigned to the variable file.

  2. The test command follows the if. If $file (after variable substitution) is a file that does not exist (note the NOT operator, !), the commands under the then keyword are executed. An exit value of 1 means that the program failed in some way. (In this case, the test failed.)

  3. If the file is a directory, print that it is a directory.

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

  5. If the file is readable, writable, and executable, then . . .

  6. The fi terminates the innermost if command. The program exits with an argument of 2 if the file does not have read, write, and execute permission.

  7. The else commands are executed if lines 2 and 3 fail. The program exits with a value of 3.

  8. This fi goes with the if on line 3 in the example.

  9. The file called testing does not exist.

  10. The $? variable holds the exit status, 1.

12.5.15 The null Command

The null command is a colon. It 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 the loop command to make the loop a forever loop or for testing variable expression modifiers such as {EDITOR:–/bin/vi}.

Example 12.25.

(The Script)

1   name=Tom

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

    then

3       :

4   else

         print "$1 not found in databasefile"

         exit 1

    fi


EXPLANATION

  1. The string Tom is assigned to the variable name.

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

  3. The colon is the null command. It always exits with 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 12.26.

(The Script)

1    : ${EDITOR:=/bin/vi}

2    echo $EDITOR


EXPLANATION

  1. The colon command takes an argument that is evaluated by the shell. The expression ${EDITOR:=/bin/vi} is used as an argument to the colon command. If the variable EDITOR has been previously set, its value will not be changed; if it has not been set, the value /bin/vi will be assigned to it. The Korn shell would have responded with an error such as ksh: /bin/vi: not found if the colon command had not preceded the expression.

  2. The value of the EDITOR variable is displayed.

12.5.16 The case Command

The case command is a multiway branching command used as an alternative to the 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, instruction starts after the word esac (case spelled backwards).

If a case variable is not matched, the program executes commands after the *), the default value, until the double semicolons or esac is reached. The *) value serves the same purpose as the else statement in if/else conditionals. The case values can use 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 12.27.

(The Script)

     #!/bin/ksh

     # Scriptname: xtermcolor

     # Sets the xterm foreground color (the color of the prompt and

     # input typed for interactive windows.

1    read color?"Choose a foreground color for your terminal?"

2    case "$color" in

3    *[Bb]l??)

4        xterm -fg blue -fn terminal &

5            ;;

6    *[Gg]reen)

          xterm -fg darkgreen -fn terminal &

             ;;

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

         xterm -fg "$color" -fn terminal &

             ;;

8   *)  xterm -fn terminal &        # default

             ;;

9   esac

10  print "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 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.

  4. The statement is executed if the value in line 3 matches the case expression.The xterm command sets the foreground color to blue.

  5. The double semicolons are required after the last command in this block of commands. Control branches to line 10, after the semicolons are reached.

  6. If the case expression matches a G or g, followed by the letters r–e–e–n, the xterm window foreground color is set to dark green. 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 xterm command is executed.

  8. This is the default value. If none of the above values match the case expression, the command(s) after the *) value are executed. The default color for the terminal foreground is black.

  9. The esac statement (case spelled backwards) terminates the case command.

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

The case Command and the here document

Often, the here document is used to create a menu. After the user has selected a choice from the menu, the case command is used to match against one of the choices. The Korn shell also provides a select loop for creating menus.

Example 12.28.

(The .profile File)

     print "Select a terminal type "

1    cat << EOF

         1) vt120

         2) wyse50

         3) ansi

         4) sun

2    EOF

3    read TERM

4    case "$TERM" in

     1)  export TERM=vt120

         ;;

     2)  export TERM=wyse50

         ;;

     3)  export TERM=ansi

         ;;

     *)  export TERM=sun

         ;;

5    esac

     print "TERM is $TERM"


EXPLANATION

  1. A here document is used to display a menu of choices.

  2. EOF is the user-defined terminator. Input for the here document stops here.

  3. The read command waits for user input and assigns it to the TERM variable.

  4. The case command evaluates the variable TERM and matches it against one of the numbers in the list. If a match is found, the terminal is set.

  5. The case command terminates with esac.

    Previous Section  < Day Day Up >  Next Section