Control Commands

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 ina )   print — "—a flag given" ;;b )   print — "—b flag given" ;;
						–c )   print — "—c flag given" ;;
						esac
						$ checkargs —bb flag given
						$ checkargs —aa 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–9])*([0–9]), will match any number 1–9999999*. 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 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 character. Multiple case patterns can be given, as long as they are separated with a | character. For example, the pattern

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
					

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.78.56