Control Commands

   

Korn Shell: Unix and Linux Programming Manual, Third Edition, The
By Anatole Olczak

Table of Contents
Chapter 8.  Writing Korn Shell Scripts


The Korn shell provides a number of control-flow commands typically found in high-level programming languages. The following sections cover these special commands.

The case Command

The case command provides multiple-branch capability. It is used to compare a single value against a number of other values. Commands associated with that value are executed when a match is made. The syntax for the case command is:

 case value in         pattern1)       command                         command;;         pattern2)       command                         command;;         . . .         patternn)       command                         command;;  esac 

where value is compared to pattern1, pattern2, ... patternn. When a match is found, the commands associated with that pattern up to the double semi-colons are executed.

The following Korn shell script demonstrates a simple case statement. It successively compares the command-line argument given to a, b, or c and then prints out which flag was given. First, it checks to make sure that at least one command-line argument is given:

 $ cat checkargs  (($#<1)) && { print Not enough args; exit 1; }  case $1 in        ?span>a )   print ?" a flag given" ;;        ?span>b )   print ?" b flag given" ;;         c )   print ?" c flag given" ;;  esac  $ checkargs  b  ?span>b flag given  $ checkargs  a  ?span>a flag given 

The d argument is given in the next invocation. It doesn't match any of the patterns, so nothing is printed:

 $ checkargs  d  $ 
Specifying Patterns with case

The same patterns used in file name substitution can also be used in case statements to match patterns. For example, the * pattern is frequently used to specify a default pattern. It will always match if no other match is made. Another pattern, @([1?])*([0?]), will match any number 1?999999*. Let's expand the checkargs script to match on any type of argument using some special pattern-matching characters.

 $ cat checkargs  case $1 in         @([a-z]))               print "Lowercase argument given: $1" ;;         @([A-Z]))               print "Uppercase argument given: $1" ;;        @([1-9])*([0-9]))               print "Integer argument given: $1" ;;        "" )   print "No argument given" ;;        * )    print "Invalid argument given" ;;  esac 

Here is sample output:

 $ checkargs  a  Lowercase argument given:  a  $ checkargs 99  Integer argument given: 99  $ checkargs  C  Uppercase argument given:  C  $ checkargs 0  Invalid argument given  $ checkargs  No argument given 

Notice that the @(a z) and @(A Z) patterns cause a ?/span> followed by only one character to be matched. An argument like axyz would cause the invalid argument message to be printed:

 $ checkargs  axyz  Invalid argument given! 

The ?([A z]) case pattern would allow for multiple characters to follow the ?/span> character. Multiple case patterns can be given, as long as they are separated with a | character. For example, the pattern

 ?span>a |  b |  c 

would match a, b, or c. The new Korn shell pattern matching formats also allow multiple case patterns to be given like this:

 ?(pattern1 | pattern2 | ... | patternn) 

matches zero or one occurrence of any pattern

 *(pattern1 | pattern2 | ... | patternn) 

matches zero or more occurrences of any pattern

 @(pattern1 | pattern2 | ... | patternn) 

matches exactly one occurrence of any pattern

 +(pattern1 | pattern2 | ... | patternn) 

matches one or more occurrence of any pattern

 !(pattern1 | pattern2 | ... | patternn) 

matches all strings except those that match any pattern

The for Command

The for command is used to execute commands a specified number of times. In programming terminology, this iterative execution of commands is called a loop, so you may also hear the for command referred to as a for loop. The basic syntax for the for command is:

 for variable in word1 word2 . . . wordn  do         commands  done 

The commands are executed once for each word, and for each execution, variable is set to word. So if there were three words, the commands would be executed three times, with variable set to word1 in the first execution, word2 in the second execution, and word3 in the third and last execution. Here is a simple for loop:

 $ cat floop  integer LOOPNUM=1  for X in A B C  do        print "Loop $LOOPNUM: X=$X"        ((LOOPNUM+=1))  done 

When executed, it prints out the loop number and the value of X for each loop.

 $ floop  Loop 1: X=A  Loop 2: X=B  Loop 3: X=C 

Remember the kuucp script that we wrote earlier in this chapter? We could use it with a for loop to uucp multiple files to a remote system like this:

 $ for FILE in chap1 chap2 chap3  > do  >     print "Copying $FILE to ukas"  >     kuucp $FILE ukas  > done  Copying chap1 to ukas  Copying chap2 to ukas  Copying chap3 to ukas 

Notice that this for loop was run from the prompt, and not from a script. Korn shell control commands are like any other commands, and can be entered at the command prompt. This is useful when you want to run something quickly without having to edit a file.

File name substitution, command substitution, and variable substitution can also be used to generate a list of word arguments for the for command. The first line of the previous command could have been given as:

 for FILE in chap[1-3] 

or

 for FILE in $(ls chap[1-3]) 

or

 CHAPS=$(ls chap[1-3])  for FILE in $CHAPS 

The $* and $@ variables can be used to loop on command-line arguments like this:

 for variable in $*  do         commands  done 

This is the same as:

 for variable in $1 $2 $3 . . .  do         commands  done 

This idea could be used to make a Korn shell script that uucp's a variable number of files to ukas:

 $ cat myuucp  for FILE in $*  do        print "Copying $FILE to ukas"        kuucp $FILE ukas  done 

Now to uucp just one file to ukas:

 $ myuucp chap1  Copying chap1 to ukas 

or all the chap files to ukas:

 $ myuucp chap*  Copying chap1 to ukas  Copying chap2 to ukas  Copying chap3 to ukas  ... 

With no argument, nothing is displayed:

 $ myuucp  $ 
Other for Syntax

The for command can also be used without the list of word arguments:

 for variable  do         commands  done 

The commands are executed once for each positional parameter, and variable is set to each successive positional parameter. It is equivalent to:

 for variable in "$@"  do         commands  done 

The myuucp script could be modified to use this format and still do the same thing.

 $ cat myuucp  for FILE  do        print "Copying $FILE to ukas"        kuucp $FILE ukas  done 

Use this format to enter the for command on one line:

 for var in word1 word2. . . wordn; do commands; done 

or

 for var; do commands; done 

Notice the ; character before the do and done commands. This is needed so that the do and done commands are separated from the previous commands.

The if Command

The if command is used to execute commands if a given condition is true. The basic syntax of the if command is:

 if command1  then         commands  fi 

If command1 returns a zero exit status, then the commands between then and fi are executed. Otherwise, the commands are skipped. For example, if ANSWER is set to YES, then the print command is executed.

 if [[ $ANSWER = YES ]]  then        print "Ok, the answer is $ANSWER"  fi 

Here is a Korn shell script that uses the if command to check if a file exists, before trying to copy it.

 $ cat fileck  FILE=$1  if [[ -f $FILE ]]  then        print "Copying $FILE to PUBDIR"        cp $FILE /usr/spool/uucppublic  fi 

We could add another if command to check the number of arguments. If there is less than one command-line argument, a usage message is printed and the script exits.

 $ cat fileck  if (($# < 1))  then        print "Usage: $0 file"        exit 1  fi  FILE=$1  if [[  f $FILE ]]  then        print "Copying $FILE to PUBDIR"        cp $FILE /usr/spool/uucppublic  fi 

The command-line argument check in fileck could have been written using the && operator like this:

 (($# < 1)) && {print "Usage:$0 file"; exit 1;} 

This version is more compact, albeit less readable than the previous one using the if command.

Use this format if you want to give an if command on one line:

 if command1; then command2; fi 

The ; characters are needed to separate then and fi from the previous commands.

Other if Syntax: else

This form of the if command is used to execute one set of commands if a condition is true, or another set of commands if the condition is not true.

 if command1  then        commands  else        commands  fi 

If command1 returns a zero exit status, then the commands between then and else are executed. If command1 returns a non-zero exit status, then commands between else and fi are executed. In this example, if ANSWER is YES, then the print command is executed. Otherwise, it exits:

 if [[ $ANSWER = YES ]]  then        print "Ok, the answer is $ANSWER"  else        exit 1  fi 

We could add the else part to the if command in fileck to make sure the file existed before it was copied:

 $ cat fileck  if (($# < 1))  then        print "Usage: $0 file"        exit 1  fi  FILE=$1  if [[  f $FILE ]]  then        print "Copying $FILE to PUBDIR"        cp $FILE /usr/spool/uucppublic  else        print "$FILE non-existent"        exit 2  fi 

Here is some sample output:

 $ fileck  Usage: fileck file  $ fileck nofile  nofile non-existent  $ fileck log.out  Copying log.out to PUBDIR 

Notice that exit 1 was used for a usage error, while exit 2 was used for the non-existent file error. In Korn shell scripts, especially large ones, it's a good idea to use different exit codes for different types of error conditions. It can be helpful in debugging.

 $ fileck; print $?  Usage: fileck file  1  $ fileck nofile; print $?  nofile non-existent  2  $ fileck log.out; print $?  Copying log.out to PUBDIR  0 
Other if Syntax: elif

This form of the if command is used to execute one set of commands if one condition is true, another set of commands if another condition is true, and so on, or else execute a set of commands if none of the conditions are true. The syntax for this if command is:

 if command1  then         commands  elif command2  then         commands  . . .  elif commandn  then         commands  else         commands  fi 

If command1 returns a zero exit status, or command2 returns a zero exit status, or commandn returns a zero exit status, then execute the commands corresponding to the if/elif that returned a zero exit status. Otherwise, if all the if/elif commands return a non-zero exit status, execute the commands between else and fi. This if format is much easier to explain with an example. The following Korn shell script checks how many users are logged on, and prints the appropriate message:

 $ cat whonu  USERS=$(who | wc  l)  if ((USERS == 1))  then        print "There is 1 user logged on."  elif ((USERS == 2))  then        print "There are 2 users logged on."  elif ((USERS == 3))  then        print "There are 3 users logged on."  else        print "More than 4 users are logged on."  fi 

If USERS equals 1, 2, or 3, then the corresponding if/elif..then clause is executed:

 $ whonu  There are 3 users logged on. 

Otherwise, the else clause is executed:

 $ whonu  More than 4 users are logged on. 
if/elif vs case

When there are more than a few conditions to check, the case statement should be considered instead of if/elif. Not only is it more readable, but less code is actually needed. The whonu script could be written using a case statement like this:

 USERS=$(who | wc  l)  case $USERS in        1 )    print "There is 1 user logged on" ;;        2 )    print "There are 2 users logged on" ;;        3 )    print "There are 3 users logged on" ;;        * )    print "There are 4 or more users \               logged on" ;;  esac 

The whonu script had thirteen lines of code using if/elif/else, and only seven lines of code using a case statement.

The while Command

Here is another type of looping command. The syntax for the while command is:

 while command1  do         commands  done 

where command1 is executed, and if the exit status is zero, the commands between do and done are executed. Command1 is executed again, and if the exit status is zero, the commands between do and done are also executed again. This continues until command1 returns a non-zero exit status. The listargs script loops on the command-line arguments. For each loop, the positional parameter $1 is displayed, then the positional parameters are shifted. This continues until the number of positional parameters is 0.

 $ cat listargs  while (($# != 0))  do        print $1        shift  done 

In the first loop, $# equals 4, so the value of $1 is printed and the positional parameters are shifted left once. In the second loop, $# equals 3 and the loop commands are executed again. This continues until the fourth loop, where after the print command, shift sets $# to 0. Back at the top of the loop on the fifth try, $# is now 0, so the commands between do and done are skipped. Execution continues with commands following done.

 $ listargs A B C D  A  B  C  D 

The following while command loops until there is no LOCKFILE. Every 30 seconds, it wakes up to check if it's still there.

 $ while [[  f LOCKFILE ]]  > do  >     print "LOCKFILE still exists"  >     sleep 30  > done  LOCKFILE still exists  LOCKFILE still exists  . . . 

You've heard the term "stuck in an endless loop". Well, here is one for you. This command will loop forever, since the true command always returns a zero exit status:

 $ while true  > do  >     print "Looping forever..."  > done  Looping forever...  Looping forever...  Looping forever...  . . . 

To give the while command on one line, use this format:

 while command1; do commands; done 

Just like with if and for command on-line formats, the ; characters are needed to separate do and done from the previous commands.

The until Command

The until command is another looping command. It's like the while command, except that instead of looping while the condition is true, it loops while the condition is false. So you can think of it as the opposite of the while command. The syntax for the until command is:

 until command1  do         commands  done 

where commands are executed until command1 returns a zero exit status. To demonstrate the differences between the until and while commands, let's rewrite the listargs script. In this version that uses the until command, $# is checked if it equals 0 before looping. This is in contrast to the while version that checked if $# was not equal to 0 before looping.

 $ cat listargs  until (($# == 0))  do   print $1   shift  done 

Here is sample output:

 $ listargs A B  A  B 

Just to prove that almost any while loop can be rewritten using until, let's take the second example from the while section and rewrite it. Now instead of looping while LOCKFILE exists, we loop until it is non-existent:

 $ until [[ !  f LOCKFILE ]]  > do  >     print "LOCKFILE still exists"  >     sleep 30  > done  LOCKFILE still exists  LOCKFILE still exists  . . . 

Even the forever loop can be rewritten using until:

 $ until false  > do  >     print "Looping forever..."  > done  Looping forever...  Looping forever...  Looping forever...  . . . 

Nested Loops

There is another type of loop that is used to loop inside of another loop. In programming terms, this is called a nested loop. For example, in this script, the loops work together to count from 10 to 35 in increments of 5. The for i in 1 2 3 is called the outer loop, and the for j in 0 5 is called the inner loop.

 $ cat nloop  for i in 1 2 3  do        for j in 0 5        do              print "$i$j"        done  done 

For each outer loop, the inner loop is executed twice. So, the first inner loop sets j to 0, and the second inner loop sets j to 5. This is repeated for each outer loop. The output explains this much better.

 $ nloop  10  15  20  25  30  35 

Breaking Out of Loops

You may want to exit from a loop before the loop condition is satisfied. This is where the break command comes in. It causes an exit from a loop-type command, but not from the entire script. Once you break from a loop, execution continues with the next command following the loop. For example, we could change the listargs script so that if a character argument was not given, the while loop would be terminated.

 $ cat listargs  while (($# != 0))  do        if [[ $1 = +([A-z]) ]]        then              print "$1: arg ok"              shift        else              print "$1: Invalid argument!"              break        fi  done  print "Finished with args"  . . . 

Here is sample output. Notice that the command following the while loop is executed after the break. If you wanted to terminate the entire script, exit would have to be used instead of break.

 $ listargs A 1 B  A: arg ok  1: Invalid argument!  Finished with args 

The break command can also be used to exit from a nested loop using this format:

 break n 

where n specifies the nth enclosing loop to exit from. Here is a new version of the nloop script that breaks out of both loops if i equals 2 and j equals 0:

 $ cat nloop  for i in 1 2 3  do        for j in 0 5        do              if ((i == 2 && j == 0))              then                    break 2              else                    print "$i$j"              fi        done  done 

Now the output would be:

 $ nloop  10  15 

If break was used instead of break 2, then only the inner for loop would have been terminated, and execution would have continued with i set to 3 in the outer loop, and j set to 0 in the inner loop.

The continue Command

The continue command causes execution to continue at the top of the current loop. It's like the break command, except instead of exiting from the loop completely, only the remaining commands in the current loop are skipped. Let's change the listargs script so that instead of exiting on an invalid argument, it just prints out the error message, but continues execution.

 $ cat listargs  while (($# != 0))  do        if [[ $1 = +([A-z]) ]]        then              print "$1: arg ok"              shift        else              print "$1: Invalid argument!"              shift              continue        fi  done  print "Finished with args"  . . . 

Here is more sample output. Notice that this time, even though an invalid argument was given, the next command-line argument was processed.

 $ listargs A 1 B  A: arg ok  1: Invalid argument!  B: arg ok  Finished with args 

Like with the break command, an integer argument can be given to the continue command to skip commands from nested loops.

The select Command

This is the last loop command we'll talk about. The select command is used to display a simple menu that contains numbered items, and a prompt message. The syntax for the select command is:

 select variable in word1 word2. . . wordn  do         commands  done 

where word1 through wordn are displayed as numbered menu choices followed by a prompt (default #?). If the response is in the range 1 through n, then variable is set to the corresponding word, REPLY is set to the response, and the commands are executed. Execution continues until a break, exit, return, or EOF is encountered. Here is a simple select command that displays three numbered choices: Choice-A, Choice-B, and Choice-C.

 $ cat stest  select i in Choice-A Choice-B Choice-C  do        print "You picked selection $REPLY: $i"  done 

At the first prompt, selection 1 is entered, so REPLY is set to 1, i is set to Choice-A and the value is printed. At the next prompt, selection 3 is entered, so REPLY is set to 3, i is set to Choice-C and the value of i is displayed again.

 $ stest  1) Choice-A  2) Choice-B  3) Choice-C  #? 1  You picked selection 1: Choice-A  #? 3  You picked selection 3: Choice-C 

Here the <RETURN> key is pressed, so the menu is just redisplayed:

 #? <RETURN>  1) Choice-A  2) Choice-B  3) Choice-C 

What if we enter an invalid choice?

 1) Choice-A  2) Choice-B  3) Choice-C  #? 5  You picked selection 5:  #? 

The print command was still run, but because an invalid choice was given, i was not set to anything. Let's add an if command to check the value inside the loop.

 $ cat stest  select i in Choice-A Choice-B Choice-C  do        if [[ $i = Choice-[A-C] ]]        then              print "You picked selection $REPLY: $i"        else              print "$REPLY: Invalid choice!"              continue        fi  done 

Now it works!

 $ stest  1) Choice-A  2) Choice-B  3) Choice-C  #? 2  You picked selection 2: Choice-B  #? 5  5: Invalid choice!  #? 1  You picked selection 1: Choice-A 

A different prompt can be used by setting the PS3 variable like this:

 $ typeset  x PS3="Enter selection>" 

Now when stest is run again, the new message prompt is displayed:

 $ stest  1) Choice-A  2) Choice-B  3) Choice-C  Enter selection>3  You picked selection 3: Choice-C 

The select and case commands are used in this Korn shell script to provide a simple menu interface to a few Unix commands. Notice that the LIST FILES portion runs in a subshell so that the prompt (PS3) can be changed without having to reset it each time for the rest of the script.

 $ cat smenu  PS3="Enter selection>"  select CMD in "CURRENT DIRECTORY NAME" \  "LIST FILES" MAIL DONE  do       case $CMD in            CURRENT* )                 pwd ;;            LIST* )                 (  PS3="List which directory?"                 select DIR in HOME PUBDIR TOOLS DONE                 do                      case $DIR in                        HOME )                              ls $HOME ;;                        PUBDIR)                              ls $PUBDIR ;;                        TOOLS )                              ls ~/tools ;;                        DONE ) break ;;                        * )    print "Bad choice"                              break ;;                      esac                 done  ) ;;            MAIL )                 mail ;;            DONE )                 break ;;            * )  print "Invalid choice"                 break ;;       esac  done 

The first menu displays three numbered choices: CURRENT DIRECTORY NAME, LIST FILES, MAIL, and DONE. If you enter 1, pwd is executed:

 $ smenu  1) CURRENT DIRECTORY NAME  2) LIST FILES  3) MAIL  4) DONE  Enter selection>1  /home/anatole/tbin 

If 2 is given, then another menu is displayed that gives you four numbered choices: HOME, PUBDIR, TOOLS, and DONE. Choices 1 through 3 cause contents of a directory to be listed, while choice 4 takes you back to the main menu.

 1) CURRENT DIRECTORY NAME  2) LIST FILES  3) MAIL  4) DONE  Enter selection>2  1) HOME  2) PUBDIR  3) TOOLS  4) DONE  List which directory?1  NEWS  dialins             nohup.out    proc.doc  asp          mail         pc           tools  bin          newauto.bat               pers  List which directory?4  1) CURRENT DIRECTORY NAME  2) LIST FILES  3) MAIL  4) DONE  Enter selection> 

If 3 is entered, mail is invoked, and if 4 is entered, we exit from the script:

 Enter selection>3  No mail.  1) CURRENT DIRECTORY NAME  2) LIST FILES  3) MAIL  4) DONE  Enter selection>4  $ 
Other select Syntax

The select command can also be used without the list of word arguments:

 select variable  do         commands  done 

It functions the same way as the previous select syntax, except that the positional parameters are displayed as numbered menu choices from 1 to n, instead of the words from the word list. It is equivalent to:

 select variable in "$@"  do         commands  done 
The select Menu Format

The format of the select menu can be controlled by assigning values to the LINES and COLUMNS variables. The LINES variable specifies the number of lines to use for the menu. The menu choices are displayed vertically until about two-thirds of lines specified by LINES are filled. The COLUMNS variable specifies the menu width. This example displays how the COLUMNS and LINES variables affect a select menu. With the default setting, the stest menu is displayed like this:

 $ stest  1) Choice-A  2) Choice-B  3) Choice-C  Enter selection> 

If LINES is set to 2, the menu choices are displayed on one line:

 $ typeset  x LINES=2  $ stest  1) Choice-A           2) Choice-B 3)     Choice-C  Enter selection> 

while if COLUMNS is set to 50, the menu choices are displayed closer together on one line:

 $ typeset  x COLUMNS=50  $ stest  1) Choice-A  2) Choice-B  3) Choice-C  Enter selection> 

If these variables are not explicitly set, the default used is 80 for COLUMNS, and 24 for LINES.

Comments

Comments are used in Korn shell scripts for debugging and documentation purposes. They provide a way to include text that is not executed. Good commenting makes it easier for you or someone else to read your scripts. Words beginning with # up to end of the current line are treated as comments and ignored. The listargs script could be commented like this:

 $ cat listargs  #      listargs ?List arguments  # Loop on each argument  while (($# != 0))  do        # Make sure argument is alphanumeric        if [[ $1 = +([A-z]) ]]        then              print "$1: arg ok"              shift        else              print "$1: Invalid argument!"              shift              continue        fi  done 

The # character can also be used to make your Korn shell scripts compatible with other shells. Any Korn shell script that begins with:

 #!interpreter 

is run by the given interpreter. So, for the C and Bourne shell users on your system, if you want your shell scripts to be run by the Korn shell (assuming it is installed in /bin), make sure they start with this:

 #!/bin/ksh 

       
    Top
     



    Korn Shell. Unix and Linux Programming Manual, Third Edition
    Korn Shell. Unix and Linux Programming Manual, Third Edition
    ISBN: N/A
    EAN: N/A
    Year: 2000
    Pages: 177

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net