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 ShellBefore 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 ArithmeticThe 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.
ArraysThe 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:
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
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:
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 SubstitutionThe 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 getoptsThe 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
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). |