Advanced Features of Korn Shell Scripting


The Korn shell, which can be installed separately as discussed in Chapter 8, contains some advanced features that can make shell programming easier and more powerful, such as built-in arithmetic and arrays. You should think carefully before using these features in a script, however, because virtually every UNIX system can run a Bourne shell script, but Korn shell scripts won't run unless the Korn shell is installed and available. If such a script is highly specializedrequiring the advanced features of the Korn shelland will be run only on your local system or on systems that have the Korn shell available, using this script won't present any problems. If you are writing a shell script for public consumption, however, or if this script will run on multiple systems within your organization and you cannot be sure which shells will be available on those systems, you are better off sticking to plain-old Bourne script, or looking ahead to Perl (see Chapter 11, "Introduction to Perl Programming").

That being said, this section will cover some of the advanced features of Korn shell scripting.

Tip

Most of the techniques given in this chapter also apply to the POSIX shell that FreeBSD ships with as /bin/sh. However, you definitely should not use these in a /bin/sh script if the script will be run on other systems that do not use a POSIX shell as /bin/sh. Because of the #!/bin/sh at the beginning of the script, the other system will try to run the script anyway. But it will bomb as soon as it hits code that it doesn't understand because that code is not part of the Bourne shell syntax.


Tip

Note that the bash shell can also run most scripts written for the Korn shell. You will, of course, need to change the first line in the script to point to the bash shell instead of the Korn shell. For example, in FreeBSD you would change #!/usr/local/bin/ksh to #!/usr/local/bin/bash. If you wanted to run the script on a Linux system where bash is installed by default in /bin, you would change the line to #!/bin/bash. A more general technique is to use #!/usr/bin/env bash, which will work equally well for all systems.


Obtaining and Installing the Korn Shell

Before you can write shell programs for the Korn shell, you need to install a copy of the Korn shell on your system. The Public Domain Korn shell (pdksh) is available for FreeBSD both on the DVD as a package under shells (which can be installed from Sysinstall or from the command line) and also in the ports tree under /usr/ports/shells/pdksh. To tell your program to run with the Korn shell, replace #!/bin/sh at the top of your shell program with #/usr/local/bin/ksh. The Korn shell is completely backward-compatible with Bourne, so any script that was written for a Bourne shell will also run under the Korn shell. The reverse is not true, however: you cannot run a Korn shell script under Bourne.

When you have the Korn shell installed, take a moment to review some of the advanced features that make programming in the Korn shell easier, more powerful, and more efficient.

Built-in Arithmetic

The Korn shell has built-in arithmetic. This means you do not need to call the expr program to do arithmetic in Korn as you do in Bourne. However, like expr, Korn shell arithmetic is limited to operations on integer numbers. Because it is an internal function, however, it can perform these operations much faster than exec.

There are two ways to access the built-in arithmetic functions of the Korn shell. The first is with the let statement. Here's an example:

let x=7+5


This line assigns 12 to the variable x.

The other method is by enclosing the expression in double parentheses, as shown here:

((x=7+5))


This can be more readable than using let. It is also more readable for mathematical comparisons than using the test command in Bourne shell syntax because it allows you to use the familiar mathematical notations < and > rather than -lt and -gt. Also, unlike with expr, characters that would have special meaning to the shell do not have to be escaped. In Korn, you can write ((5 * 3)) rather than expr 5 \* 3, for example.

Korn shell arithmetic supports the common mathematical operators shown in Table 10.5.

Table 10.5. Korn Shell Math Operators

Operator

Description

+

Addition

-

Subtraction

*

Multiplication

/

Division

%

Modulus (returns the remainder of a division)

>

Greater than

<

Less than

>=

Greater than or equal to

<=

Less than or equal to

==

Equal to

!=

Not equal to

&&

True if both expressions are non-zero

||

True if either expression is non-zero

=

Assigns the expression on the right to the expression on the left

+=

Adds the expression on the right to the variable on the left and then stores the result in the variable

-=

Subtracts the expression on the right from the variable on the left and then stores the result in the variable

*=

Multiplies the expression on the right by the variable on the left and then assigns the result to the variable

/=

Divides the expression on the right by the variable on the left and then assigns the result to the variable

%=

Divides the expression on the right by the variable on the left and then assigns the remainder to the variable


Arrays

The Korn shell also supports arrays. An array (also often described as a list) is a variable that contains multiple elements, each of which contains a separate value. Arrays are useful for grouping related elements together.

You can think of an array as a box with different compartments in it. Each compartment has a number. You could access the various compartments by giving the name of the box, followed by the compartment number.

Use the set command to load an array. For example, suppose you want to create an array called temperature that contains the average temperature for each month of the year for a given area. The following command will do the trick (the A option effectively sets the parameter, or name, of the array to temperature):

set -A temperature 57 52 58 61 63 65 71 70 68 66 64 62


This will create an array called temperature with 12 elements in itone for each month of the year. If you are following along on your system, simply enter the preceding command from a Korn shell command prompt for now (if you need to start a Korn shell, you can do so by typing ksh or pdksh at the prompt).

To access the various elements in the array, you use a subscript appended onto the end of the array name. Elements in the array start at 0, and wildcards are accepted. Here are some examples of how this works:

ksh$ echo ${temperature[0]} 57 ksh$ echo ${temperature[11]} 62 ksh$ echo ${temperature[*]} 57 52 58 61 63 65 71 70 68 66 64 62 ksh$


Arrays can contain up to 512 elements (0511).

If you want to change the value of only one element in an array, you can do so by referencing the element in a variable assignment. To set the value of the first element to 55, do the following:

temperature[0]=55


Here are a couple of points to note regarding arrays:

  • Unlike with scalar variables, the curly braces are not optional when referencing the arraythey're mandatory. echo $temperature[1] will not have the desired result. It must be written as echo ${temperature[1]}.

  • Arrays cannot be exported as environment variables.

You could have created a separate variable for each month, of course, but the program shown in Listing 10.24 shows how using arrays instead can save a lot of programming time.

Listing 10.24. Arrays in a Korn Shell Script

1.  #!/usr/local/bin/ksh 2.  # Demonstration of arrays and computing average temperature. 3.  set -A temperature 57 52 58 61 63 65 71 70 68 66 64 62 4.  i=0 5.  printf "\nMonth\t\tTemperature\n\n" 6.  while ((i < 12)) 7.  do 8.          (( month = $i + 1 )) 9.          printf "$month\t\t${temperature[$i]}\n" 10          (( total_temp += ${temperature[$i]} )) 11.         (( i += 1 )) 12. done 13. avg_temp=$(( total_temp / 12 )) 14. echo 15. echo "Average temperature for whole year: $avg_temp" 16. echo

Here's the output of this program:

Month           Temperature 1               57 2               52 3               58 4               61 5               63 6               65 7               71 8               70 9               68 10              66 11              64 12              62 Average temperature for whole year: 63


This may look scary at first, but really there isn't much new here other than the syntax, which is a little different from what you're accustomed to using in Bourne shell scripting. There is also some new logic in this program that you haven't used before, but it will make perfect sense after you look at how the program works:

  • Line 1 Notice the difference here: this shell script starts with /usr/local/bin/ksh. If you accidentally put #!/bin/sh here instead, the program will not run, and you will get strange errors.

  • Line 3 This line uses the set command to load an array named temperature with 12 numbers, each of the numbers being an average temperature for one month.

  • Line 4 This simply initializes the loop counter to zero.

  • Line 6 This line starts a loop that will continue as long as i is less than 12.

  • Line 8 This line creates a new variable called month and sets the value to whatever is in i, incremented by 1. This is used in the output of the program to print the number of the current month. Why do you have to add 1 to i? Because i is currently set to 0, and it is what you will use to get the first element out of the temperature array. Because array element numbers start with 0, your elements in the temperature array are numbered from 0 to 11. But this is not what you want for your month number display, so you simply add 1 to the current value of $i for each iteration of the loop.

  • Line 9 This line prints the current value of month, followed by two tab characters and then a single element from the temperature array. Notice that you use variable substitution here. The value of i is expanded to the number stored in i, and this is used as the element to retrieve from the array.

  • Line 10 This line takes whatever value is currently in the variable total_temp, adds the number stored in the array element of temperature represented by i to it, and then stores the new value in total_temp, overwriting the old value. It keeps a running sum of all the temperatures added together, which will be used later to compute the average temperature.

  • Line 11 This adds 1 to the current value of i. So, for example, on the second iteration of the loop, i will now be set to 1 instead of 0, and therefore the second element of the array will be printed as well as added to total_temp. This is the last statement in the loop. If i is still less than 12, the loop will repeat.

  • Line 13 After the loop has completed, the average temperature is computed by taking the sum of all the temperatures stored in total_temp and dividing by 12. The new value is assigned to avg_temp.

  • Line 15 The average temperature for the entire year is printed.

Note

According to the program, the average temperature for the whole year was 63. This is a good example of how the shell's arithmetic can only handle integers. The actual average temperature for the whole year is 63.083333, but because the shell cannot handle floating-point math, it drops the decimal portion and just prints 63. If you needed more precision here, you could use the bc command to do the calculation, as you saw in Listing 10.6, to compute the circumference and area of circles with π.


Do you see how this program saved us some work? Rather than having to go through each month manually, you were able to use a loop that increments a variable and automates the task of getting the value for each month. If you had used separate variables to store the temperatures, this task would have required a good deal more code than you used here.

Note

If you want some good practice, see if you can modify the previous temperature program to use a for loop instead of a while loop. Here is a hint: Remember that the array can accept the * wildcard to show all the elements in the array.


Command Substitution

The Korn shell also supports a cleaner form of command substitution than the Bourne shell. Rather than putting commands in backquotes, you can use a syntax like the following:

todayDate=$(date)


The old style is also still supported. Which one you use in Korn shell scripts is mostly a matter of personal preference.

Using getopts

The getopts command offers a better way of handling command-line arguments than the simple Bourne-style syntax used earlier in the chapter. It allows you to use the standard option syntax of -option that most other FreeBSD commands use. It also allows for better handling of arguments to the options.

The general syntax of the getopts command looks like this:

getopts options variable


Here, options is the list of valid options that can be supplied, and variable is the name of the variable in which those options should be stored. If an option letter ends with a colon, it can also take a value. That value will be stored in the special variable $OPTARG. Another special variable, named $OPTIND, stores the value of the current argument being worked on.

The getopt command executes once for each option it is supplied with. If used with the while loop, the loop will execute once for each command-line argument supplied. The following shows an example of how the getopt command syntax works:

getopts abc:d: myVar


With this command, the valid options that getopts recognizes are a, b, c, and d. In addition, options c and d are followed by a value that will be stored in the variable $OPTARG. The option itself is stored in MyVar.

A simple sample script using getopts appears in Listing 10.25. Note that this script will run under FreeBSD's /bin/sh as well, butagainbeware of other systems whose /bin/sh isn't POSIX and doesn't support getopts.

Listing 10.25. Using getopts to Handle Command-Line Arguments

1.  #!/usr/local/bin/ksh 2.  # Demonstrates the use of getopts. 3.  for i in 1 2 3 4 4.  do 5.    getopts abc:d: myVar 6.    echo "$myVar, $OPTARG" 7.  done

Let's assume that you have saved this script as a program called test.pdksh, and you invoke it as follows:

# ./test.pdksh -a -c foobar -b -d blah


Note that the order in which you specify the arguments does not matter. The output looks like this:

a, c, foobar b, d, blah


If you omit any arguments, the myVar variable will be set to the question mark (?). The getopts command is run as many times as there are arguments. The first time it is run, myVar will contain a. The second time it is run, myVar will contain c, and the variable $OPTARG will contain the string foobar. Using these variables and the previously mentioned $OPTIND variable, the getopts command can be used for decision-making in shell programs using the same procedures used throughout this chapter (loops, conditionals, and case statements, for example).




FreeBSD 6 Unleashed
FreeBSD 6 Unleashed
ISBN: 0672328755
EAN: 2147483647
Year: 2006
Pages: 355
Authors: Brian Tiemann

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