An exciting new addition to the capabilities of the old Bourne shell offered by the Korn shell is the capability to do arithmetic. The Bourne shell provides no built-in calculating capability, so even the simplest arithmetic requires command
substitutions that resort to calling other UNIX programs. The Korn shell adds some built-in capability to do basic arithmetic.
The two major tools you'll use when doing arithmetic inside the Korn shell are the typeset command and the let command. The typeset command provides number formatting capability and the capability to declareor set asidesome variables for the
special purpose of doing arithmetic. The let command is where all this magic really happens.
The Korn shell is still a very slow tool for doing repetitive calculations, even with the typeset statement. Floating-pointreal numbers with decimal points and fractions and the likeisn't supported. Therefore, all your calculations must use
integer values, and they will yield integer results. However, the shell arithmetic is sufficient to support programming concepts such as loop control with counters.
The typeset statement is an extension provided by the Korn shell to permit some amount of control over the format and usage of shell variables. When typeset is used for managing variables, its syntax is
typeset [ [pm]HLRZilrtux [n] ] [ name[=value] ] ...
The particular set of options that you use with the command determines the required format for the syntax of the command. Not all combinations of option letters are legal. Only the following options should be specified:
-i |
Declares the variable to be of type integer. Use the optional n to specify the number base to which the value should be converted on substitution. The number is always carried in base 10, and only base 10 decimal values should be assigned to the variable. On substitution, however, the value is converted to the equivalent octal digit string. You may also specify one of the -L, -LZ, -R, or RZ options for the named variable(s). |
-l |
The value of the named variable(s) should be converted to all lowercase letters when it is substituted. Don't specify this option together with -u. You must specify at least one name argument, and you may provide an optional initial value for some or all of the named variables. |
-r |
The named variable(s) will be treated as read-only, meaning that subsequent assignments of a value to the named variables will be inhibited. If the variable is to have a non-null value, you should supply a value for the listed variable names. You must name at least one variable to have the read-only attribute. You can use the -r option in combination with any of the other options. |
-u |
The value of the named variable(s) should be converted to all uppercase letters when it is substituted. Don't specify this option together with -l. You must specify at least one name argument, and you may provide an option initial value for some or all of the named variables. |
-x |
The named variables should be exportedmade availableto shell scripts and subshells. Note that typeset -x is the only command provided by the Korn shell for establishing exported variables. A command alias is provided automatically at start-up by the shell named export, which is equivalent to the command typeset -x. Unlike the Bourne shell export statement, which permits only variable names, the Korn shell (using command alias) supports statements of the form export name=value ..., providing an initial value for each exported variable. If the variable already exists when the typeset -x command is given, the shell adds the export attribute to the variable. If a you define a new variable but specify no value, the variable is initialized to the null string and is marked exportable. |
-L |
The value of the named variable(s) should be left-justified and padded with blanks on the right to a length of n when it is substituted. Obviously, you must specify a field length n. For example, -L4 expands the variable value to four characters on substitution. You must specify at least one name argument, and you may provide an optional initial value for some or all of the named variables. |
-LZ |
Similar to -L, but it strips any leading zeroes from the variable value before substitution. |
-R |
The value of the named variable(s) should be right-justified and padded with blanks on the left to a length of n when it is substituted. You must specify a field length n. For example, -R4 expands the variable value to four characters on substitution. You must specify at least one name argument, and you may provide an optional initial value for some or all of the named variables. Don't specify the -L or -LZ options together with -R. |
-RZ |
Similar to -R, but it pads the value with zeroes on the left. If the value of the named variable contains only digits, the result is a numeric field of length n. |
-Z |
Same as -RZ. |
-H |
The -H option is supported only by versions of the Korn shell that execute on non-UNIX operating systems. When -H is specified, each of the name variables is presumed to be used to hold a filename or pathname. Assignment of a value to the variable causes mapping of the name to filename formats compatible with the host operating system. You can then use the variable as a filename argument on subsequent commands. You must specify one or more name arguments with this option. The H option is ignored on UNIX operating systems. |
Apart from exporting variablesusually by way of the export aliasthe typeset command is mainly used for two purposes: setting up variables that you plan to use for calculation as integer variables, and defining special formatting options for
variables.
Although the Korn shell doesn't require that a variable be declared as integer to do arithmetic with it, doing so provides some advantages. Calculations are more efficient when you use arithmetic variables in the let statement, because the shell can
maintain the numeric value of the variable in an internal binary format, which is more suitable to the computer's math instructions. Likewise, there are contexts where the shell will recognize arithmetic operators in an expression if the expression
contains integer variables, but it won't if the expression uses standard variables.
The general procedure for using typeset to define integer variables is straightforward. Before using variables for calculation, simply issue a typeset command to declare the variables as integers. For example,
typeset -i x y sum read x y let sum=x+y print $sum
The Korn shell automatically defines an alias named integer which is equivalent to typeset -i:
alias integer="typeset -i"
You can use the alias to make your integer definitions more readable, as in the following revision of the previous example:
integer x y sum read x y let sum=x+y print $sum
The second use of typesetto set up output formatting options for variablesis of interest primarily to shell script writers who want to generate nicely formatted output. The formatting options -L, -R, -LZ, and -RZ are also of some use in
generating filenames. Suppose, for example, that you want to create a series of files that all end with a four-digit number. By writing the typedef statement
typeset -Z4 suffix
you can easily generate the required filenames by using code such as
typeset -Z4 suffix=0 while ... do let suffix=suffix+1 print sampfile.$suffix done
The Korn shell automatically right-justifies the value of $suffix in a four-character field and fills the number out to four digits with leading zeros. Thus, it generates the series of filenames sampefile.0001, sampfile.0002, and so on.
Use let to perform an arithmetic calculation. The syntax for the let statement, the second major element in the shell's support for arithmetic, is simple. It is
let expr
For expr, write an expression that consists of terms and operators. A term is a variable or a literal integer numberfor example, 3 or 512. A literal integer number is assumed to be written in base 10. You can specify another base using the
format radix#number, where radix is the number base, and number is the value of the number. For a radix greater than 10, digits consist of the characters 0 through 9 and A through Z. For example, in radix 16 (hexadecimal), the digits
are 0 through 9 and A through F.
Table 12.4 shows the arithmetic operators supported by the Korn shell for use in arithmetic expressions.
Operator |
Expression |
Value of Expression |
- |
-exp |
Unary minusthe negative of exp |
! |
!exp |
0 when exp is non-zero; Otherwise, 1 |
~ |
~exp |
Complement of exp |
* |
exp1 * exp2 |
Product of exp1 and exp2 |
/ |
exp1 / exp2 |
Quotient of dividing exp1 by exp2 |
% |
exp1 % exp2 |
Remainder of dividing exp1 by exp2 |
+ |
exp1 + exp2 |
Sum of exp1 and exp2 |
- |
exp1 - exp2 |
Difference of exp2 from exp1 |
<< |
exp1 << exp2 |
exp1 is shifted left exp2 bits |
>> |
exp1 >> exp2 |
exp1 is shifted right exp2 bits |
<= |
exp1 <= exp2 |
1 if exp1 is less than or equal to exp2; otherwise, 0 |
>= |
exp1 >= exp2 |
1 if exp1 is greater than or equal to exp2; otherwise, 0 |
< |
exp1 < exp2 |
1 if exp1 is less than exp2; otherwise, 0 |
> |
exp1 > exp2 |
1 if exp1 is greater than exp2; otherwise, 0 |
== |
exp1 == exp2 |
1 if exp1 is equal to exp2; otherwise, 0 |
!= |
exp1 != exp2 |
1 if exp1 is not equal to exp2; otherwise, 0 |
& |
exp1 & exp2 |
Bitwise AND of exp1 and exp2 |
^ |
exp1 ^ exp2 |
Exclusive OR of exp1 and exp2 |
| |
exp1 | exp2 |
Bitwise OR of exp1 and exp2 |
&& |
exp1 && exp2 |
1 if exp1 is non-zero and exp2 is non-zero; otherwise, 0 |
|| |
exp1 || exp2 |
1 if exp1 is non-zero or exp2 is non-zero; otherwise, 0 |
= |
var = exp |
Assigns the value of exp to identifier id |
+= |
var += exp |
Add exp to variable id |
-= |
var -= exp |
Subtracts exp from variable id |
*= |
var *= exp |
Multiplies var by exp |
/= |
var /= exp |
Divides var by exp |
%= |
var %= exp |
Assigns the remainder of var divided by exp to var |
<<= |
var <<= exp |
Shifts var left exp bits |
>>= |
var >>= exp |
Shifts var right exp bits |
&= |
var &= exp |
Assigns the bitwise AND of var and exp to var |
|= |
var |= exp |
Assigns the bitwise OR of var and exp to var |
^= |
var ^= exp |
Assigns the exclusive OR of var and exp to var |
The Korn shell also supports expression grouping using parentheses. An expression in parentheses is evaluated as a unit before any terms outside the expression are evaluated. Parentheses are used to override the normal precedence of operators.
Operators in Table 12.4 are listed in decreasing order of precedence. The Korn shell uses the normal precedence for arithmetic operators, which you know from the C programming language or from the use of an ordinary calculator. Because of these
precedence rules, the expression a+b*y is computed by first multiplying b*y, and then adding the product to a, just as though the expression had been written a+(b*y). With parentheses, you can change the order of calculation. For example, (a+b)*y would be
computed by first adding a and b, and then multiplying the sum by y.
The let command is a shell built-in command. Like any command, it sets an exit value. The exit value of the let command is 0 if the value of the last or only expression computed is non-zero. Conversely, if the last or only expression evaluates to 0, the
exit value of the let command is 1. This strange inversion is an adaptation to the if statement, where a command setting a zero exit value is truethat is, causes execution of the then clauseand a command setting a non-zero exit value is
falsethat is, causes execution of the else clause.
For example, because of the let command's inverted exit value, the statement if let "a == b", when a and b are equal, is considered true. The logical result of the equality comparison would be 1, which is equivalent to if let 1. The last
expression has a value of 1. Therefore, the exit value from let is 0, and the if statement is considered true, thus invoking the then clause as expected.
Notice that you need to quote operators used in a let expression that are special to the shell. The command let prod=x|y would give very strange results if it were written without quotes. The shell would see a pipe between the two commands let prod=x
and y. Acceptable quoting is any of the following forms:
let "prod=x|y"
let prod="x|y"
let prod=x\|y
Many Korn shell users use the convention of always quoting an expression in its entirety and, thereby, avoid the problem of shell metacharacters entirely.
Take another look at the syntax of the let command. Notice that each of its terms are arbitrary expressions. A command such as let x+y is valid, but it is ordinarily of little use. This is because the sum of variables x and y is computed but the result
is thrown away. You should use an assignment expressionfor example, let sum=x+yto retain the result of the calculation in a variable named sum for later reference. The only time when it makes sense to evaluate an expression without assigning
the result to a new variable is when the purpose of the let command is to set a command exit valuenamely, for use in statements such as if and while. In these cases, however, you can use a more convenient form of the let statement: the (( ))
expression.
A statement such as
if (( x+y < 25 )) then ... fi
is more clearly readable than the equivalent if let "x+y < 25". An additional advantage is that using quotes to hide operators is unnecessary inside an (( )) expression. The (( and )) operators are in effect a special kind of parentheses.
They notify the Korn shell that the text they enclose is intended to be an arithmetic expression; this turns off the normal interpretation of metacharacters such as < and |, and it permits the unambiguous interpretation of these symbols as operators.
Compatibility with the Bourne shell isn't compromised, for the (( and )) operators don't occur in shell scripts written for the Bourne shell.
You can use the (( )) expression form wherever the let command itself would be valid and in a number of other places as well. Unlike the let command, however, the (( )) syntax permits only one expression between the doubled parentheses.
You can use arithmetic expressions in any of the following contexts: as an array subscript, as arguments of the let command, inside doubled parentheses (( )), as the shift count in shift, as operands of the -eq, -ne, -gt, -lt, -ge, and -le operators in
test, [, and [[ commands, as resource limits in ulimit, or as the right-hand side of an assignment statementbut only when the variable name being assigned was previously defined as an integer variable with the typeset or integer statement.
Having reviewed all the basics of arithmetic in the Korn shell, you should take a look now at some specific examples. For instance,
$ x=4 y=5 $ print x+y x+y
is an example of how not to use arithmetic expressions. The first command line assigns numeric values to the non-integer variables x and y. The print line attempts to print their sum, but the print command isn't one of the places where arithmetic
expressions are supported. The result is fully compatible with the Bourne shell. The print statement simply echoes its arguments.
Now look at a first attempt to fix the problem:
$ let x=4 y=5 $ print $x+$y 4+5
The assignment statements have been changed to a let command, which has no significant affect on anything. The dollar signs on the print statement help the shell recognize that x and y are variables. The variable references are substituted with their
respective values, but the Korn shell still persists in failing to recognize the presence of an expression on the print command argument. There is, in fact, no way to get the shell to recognize an expression and to evaluate it on a print command.
Here is a working solution:
$ integer x=4 y=5 $ let sum=x+y $ print $sum 9
The key element of the solution is the use of the let statement to calculate the sum. It stores the calculated result in a new variable called sum, which can be referenced later.
You might think that using a hand calculator would be an easier way to do a simple arithmetic problem at the keyboard, and I would tend to agree with you. At the keyboard, a more effective approach is simply use the expr command. For example,
$ expr 4 + 5 9
expr achieves the same result at the keyboard, but it is of little use inside shell scripts, where the result of the expr calculationwritten to standard outputisn't readily available for use.
Now consider this example of a counter-controlled loop:
integer i=0 while (( i<5 )) do i=i+1 print $i done
This little program simply prints the numbers 1 through 5. Notice the use of an assignment statement instead of a let command to increment i. This works only because the variable i was previously declared an integer. The example works fine typed in at
the keyboard. Try it.
For a more practical example, consider the following:
$ typeset -i16 hex $ hex=125 $ print $hex 7D
Here, the variable hex has been declared to be integer and to be represented in base 16. The second line assigns a normal integer numeric value to the hex variable, and the third line prints it out. Magically, though, the effect of the 16 from the
typeset command becomes clear: The value of hex is shown in hexadecimal (base 16) notation. Going the other waythat is, converting from hexadecimal to decimalis just as easy:
$ integer n $ n=16#7d $ print $n 125
At the keyboard, once you've declared the hex and n variables, they remain in effect indefinitely. You can use them repeatedly to convert between hexadecimal. For example,
$ hex=4096; print $hex 1000 $ n=16#1000; print $n 4096
Although the main thrust of the Korn shell's features is to enhance productivity at the keyboard, the Korn shell also provides a number of boons for writing shell scripts, making the Korn shell an attractive environment for program development. In this
section, I review the Korn shell enhancements that apply to shell script writing. Of course, all the programming constructs of the Bourne shell are available, so the material in Chapter 11, "Bourne Shell," pertains equally to the Korn shell; it
won't be repeated here.
The Korn shell extensions useful in writing shell scripts are conditional expressions, which enhance the flexibility of if, while, and until statements; array variables, integer variables, extended variable reference expressions, and arithmetic
expressions; a new select statement for constructing a menu of prompts from which the user may select a choice; extended support for functions, including autoload functions; an enhanced form of the command expression$(...)that is simpler to use
than the backquoted form '...', the command operator for coprocessing|&.
The section "Variables" earlier in this chapter discussed the Korn shell's extended variable support, including array variables, integer variables, variable reference expressions, and arithmetic expressions. The other new features are
explained below.
The if, while, and until statements support two new kinds of expressions. The (( )) doubled parentheses operator, which evaluates an arithmetic expression, enables you to perform complex arithmetic tests. A zero result is considered true, and a non-zero
result is considered false. You may also write an extended conditional test expression as the argument of if, while, or until. A conditional test expression has the general form
[[ conditional-exp ]]
where conditional-exp is any of the forms shown in Table 12.2.
Notice that the conditional expression forms are similar to those of the test or [ ] expression. The Korn shell supports the test and [ ] expressions identically with how the Bourne shell does. The [[ ]] expression provides extended capabilities without
compromising compatibility with the Bourne shell.
Expression |
Condition When True |
-r file |
file exists. |
-w file |
file exists and has write permission enabled. The file might not be writable even if write permission is set, or if it is within a file system that has been mounted read-only. |
-x file |
file exists and has execute permission set. The file might not actually be executable. Directories usually have the execute permission flag set. |
-f file |
file exists and is a regular file. |
-d file |
file exists and is a directory. |
-c file |
file exists and is a character-special file. |
-b file |
file exists and is a block-special file. |
-p file |
file exists and is a named pipe. |
-u file |
The set-uid permission flag is set for file. |
-g file |
The set-group-id permission flag is set for file. |
-k file |
The sticky permission flag is set for file. |
-s file |
file has a size greater than zero. |
-L file |
file is a symbolic link. |
-O file |
file has an owner ID equal to the effective user ID of the current process. |
-G file |
file has a group ID equal to the effective group ID of the current process. |
-S file |
file is a socket. |
-t [ fildes ] |
The file descriptor fildeswhose default is 1is a terminal. |
-o option |
The named option is set. |
-z string |
string is a zero-length string. |
-n string |
string is not a zero-length string. |
string |
string is not a zero-length, or null, string. |
string = pat |
string matches the pattern pat. |
string != pat |
string does not match the pattern pat. |
s1 < s2 |
String s1 is less than string s2. That is, pat collates before s2. |
s1 > s2 |
String s1 is greater than string s2. That is, pat collates after s2. |
file1 -nt file2 |
File file1 is newer than file file2. |
file1 -ot file2 |
File file1 is older than file file2. |
file1 -ef file2 |
File file1 is the same file as file file2. |
e1 -eq e2 |
Expressions e1 and e2 are equal. |
e1 -ne e2 |
Expressions e1 and e2 are not equal. |
e1 -gt e2 |
Expression e1 is greater than e2. |
e1 -ge e2 |
Expression e1 is greater than or equal to e2. |
e1 -lt e2 |
Expression e1 is less than e2. |
e1 -le e2 |
Expression e1 is less than or equal to e2. |
The Korn shell fully supports Bourne shell functions. It also provides some extensions.
In addition to the Bourne shell syntax, the Korn shell supports the following alternate syntax for defining a function:
function identifier { command-list }
The Korn shell allows a function to have local variables. A local variable exists only during the execution of the function and is destroyed when the function returns. A local variable can have the same name as a variable in the calling environment.
During execution of the function, the local variable hides the outer variable. You define a local variable with the typeset command. For example,
function square { typeset product let "product=$1*$1" print $product return }
In the Bourne shell, traps set with the trap command remain in force after the function's return. In the Korn shell, traps set in the calling environment are saved and restored.
You can use the typeset command with option -f to manage functions. The -f option has four forms:
|
|
|
|
|
|
|
|
Autoload functions provide superior performance versus conventional shell scripts, because they are retained in memory for fast execution on repeated calls, yet unreferenced functions incur no overhead other than processing of the typeset -fu command.
You create autoload functions in much the same manner as shell scripts, except that the definition file should be in the form of a function. That is, it should begin with the statement function name. To use autoload functions, you must set the FPATH
environment variable to the directory or directories to be searchedin the same manner as you set the PATH environment variableand you must declare the functions in advance with the typeset -fu command.
Any function definition is eligible for use as an autoload function, although frequently used functions are preferred. Remember, once an autoload function has been read, its definition is retained in the shell's available memory. Large programs should
be written as conventional shell scripts instead of as autoload functions, unless the program is heavily used.
To undefine a function, use the unset command:
unset -f name ....
The named functions are purged from memory, and any typeset -fu declaration for the named function is deleted. The unset -f command is not often used, but it is particularly useful when debugging a function. Using unset -f is the only way to force the
shell to reread an autoload function definition file.
Functions are a handy way of creating new keyboard commands. Because a function executes as part of the current shell environment, a directory change made with the cd command remains in force after the function exits. This isn't true for ordinary
commands and shell scripts. Because I almost always like to take a quick peek at a directory's contents after changing to it, I created the following short function definition and added it to my login profile:
function go { cd $1 /usr/bin/ls -FC }
The go function, used in the form go dirname, not only changes to the directory but also prints a sorted listing so that I can see immediately what's in the directory.
Adding the go function to my login profile means that it's always present in the shell memory. Because go is a small function, this does no harm, considering how often I use it. For larger functions, it is better to store the function definition in a
separate file and to replace the function definition in the profile with a typeset -fu declaration, thus making the function an autoload function.
The Bourne shell provides negligible assistance with the processing of command-line options. As a result, many user-written shell scripts process options clumsily at best, and they often don't support the generalized UNIX command format for options. The
getopt command, long a standard part of the UNIX command set, helps a little. The Korn shell, however, goes one step further by adding a built-in command called getopts, which provides the same power and flexibility to script writers that C programmers
have long enjoyed.
The syntax of the getopts built-in command is straightforward:
getopts options var [ arg ... ]
For options, provide a string that defines the letters that can legally appear as command-line options. If an option letter can be followed by a value string, indicate this in the options string by following the letter with :. For example, I:
represents the option syntax -Istring.
If options begins with :, the Korn shell provides user error handling. The invalid option letter is placed in OPTARG, and var is set to ?. Without :, the getopts command issues an automatic error message on an invalid letter and sets
var to ? so that you can recognize that an error occurred and skip the invalid option, but it doesn't tell you what the invalid letter is.
For var, write the name of a variable to receive the option letter. The shell stores the letter in var when it identifies the letter as an option in the command line.
For arg, write the argument list from the command line that is to be scanned for options. The arg list is usually written in the form $* or "$@".
For reasons of practicality, the getopts command cannot scan, identify, and process all option letters in a command on one invocation. Rather, each time you call getopts, you get the next option on the command line. Of course, getopts can't look at the
real command line that invoked your shell script. It examines the arg list that you provide with getopts, stepping once through the list on each call.
When you call getopts, it starts by determining its current position in the arg list. If its current position is within a word and the word starts with -, the next character in the word is taken as an option letter. If this is your first call to
getopts or the last invocation finished scanning a word, getopts examines the next arg for a leading hyphen.
In any case, when getopts identifies an option, it stores the letter in var. If the option takes a value stringindicated in the option string by being followed by :the option value is scanned and stored in a predefined variable
named OPTARG. If getopts has started a new arg variable, it increments the predefined variable OPTIND to indicate which argument it is working on1, 2, and so on. It then updates its position in the argument list and exits.
After calling getopts, you inspect the var variable to find out which option has been identified. If the option takes a value, you'll find its value string in the predefined variable OPTARG. The return value from getopts is zero if it finds an
option, or non-zero if it can find no more options in the command-line argument list.
The code for using getopts is almost a set piece that you need to memorize. Listing 12.1 is a shell program for scanning command-line options like those you might find in a script file. Here, the example merely prints the options it recognizes.
# A routine to scan options # ... allowable options are -a, -c, -R, -Aname, or -Iname. while getopts :acRA:I: KEY $* do case $KEY in a) print Found option -a;; c) print Found option -c ;; R) print Found option -R ;; A) print Found option -A, value is "'$OPTARG'" ;; I) print Found option -I, value is "'$OPTARG'" ;; *) print -u2 Illegal option: -$OPTARG esac done # Strip option arguments, leaving positional args shift OPTIND-1 print ARGS: $*
The code in Listing 12.1 is executable. Enter the statements into a file and mark the file executable with chmod +x filename (refer to the "Keeping Secrets: File and Directory Permissions" section in Chapter 3). Then invoke the file's
name with a sample set of option letters and arguments. You'll see the shell script's idea of the options and positional arguments that you entered.
There are two special points to note about Listing 12.1. First, the option string for the getopts command begins with a colon (:). When the option string begins with a colon, the getopts command provides user error handling; an
unrecognized option letter is put into the OPTARG variable, and the var keyletter variable is set to ?. You can test explicitly for ? as the letter value, or you can simply provide your own error message for any unrecognized option letter.
If the option string doesn't begin with :, getopts provides its own error handling. Upon finding an unrecognized option letter, getopts prints an error message and sets var to ?, but it doesn't set the option letter in OPTARG. Therefore,
although you can tell that an invalid option has been found, you don't know what the invalid letter is. Of course, an invalid option letter is simply any letter that doesn't appear in the option string.
Second, note the use of the shift statement to identify the remaining position arguments from the original command line. By itself, the getopts command doesn't strip words containing options from the arg list. However, after identifying options
with getopts, you don't want to see them again when you examine the remaining positional arguments. You must throw the option words away yourself. The shift statement, inherited from the Bourne shell, does the job eminently well, assisted by the arithmetic
expression handling syntax of the Korn shell. The expression OPTIND-1 computes the number of positional arguments remaining on the command line. Notice that, because OPTIND-1 occurs in the shift command line in the position of an expression, OPTIND is
automatically recognized as a variable reference, and you don't need to write a dollar sign in front of it.
If you've ever written a shell script that enables the user to specify values either on the command line or to be prompted for them, you know what an elaborate piece of drudgery such a user-interface nicety can be. The Korn shell helps you out, though,
with a new built-in command that automates the entire processfrom printing a selection menu to prompting for the user's choice to reading it.
In fact, because the user might choose an illegal optionrequiring you to repeat the menu selection processor in case you want to display the menu repeatedly until the user decides to quit, the select statement is actually an iterative
statement, much like while or until. You must use the break statement to terminate execution of select.
The syntax of the select statement is
select identifier [ in word ... ] do command-list done
The select statement first displays the word list (word ...) in one or more columns. If the LINES variable is set and specifies an integer number, it is taken as the maximum number of lines available for displaying the word list. If there are
more items to display than this maximum, the list is broken into a multicolumn display. Each word is prefixed by a number starting at one. word may be a single word or a quoted string. It is scanned for variable and command substitutions
prior to display.
In effect, the list of strings that you specify for word ... becomes a series of menu items, which are automatically numbered and displayed for the user.
The select statement next displays the value of variable PS3 as a menu prompt. By default, the value of PS3 is #?, suggesting that the user should enter a number. If you want a different prompt, assign a value to PS3 before you execute the select
statement.
The select statement next reads a reply from the user. The entire line entered by the user is saved in the special shell variable REPLY. If the user enters a null linethat is, presses Enter or Return without typing anythingselect redisplays
the list and issues the prompt again without invoking command-list. Otherwise, if the user entered a number, the variable named identifier is set to the word corresponding to that number. That is, entering 1 sets identifier to
the first word; entering 2 sets identifier to the second word; and so on. If the number is greater than the number of words or if the user input isn't a number, select sets identifier to null. In any case, the
select statement then executes command-list.
Consider the following example, in which the user is given a choice of colors from which to select. The select statement continues to execute until the user chooses one of the allowable color names.
PS3="Select color by number (e.g., 3):" select color in Blue Green Yellow Red White Black Burnt-umber "Natural Wool" do case $color in\ Blue | Green | Yellow | Red | White | Black | Burnt-umber | "Natural Wool") break ;; *) print "Please enter a number from 1-8. Try again." ;; esac done print "Your color choice is: $color"
Notice the use of quotes to specify Natural Wool as one of the menu choices. If the words were not quoted, the select statement would view them as two separate menu items, and the user would be able to select either Natural (item 8) or Wool (item 9).
Also note that the example does nothing to execute the menu choice procedure repetitively until the user enters a valid selection. Iteration of select is automatic. It is the valid choices that must do something special to break out of the select
loopin this case, by executing the break statement.
Nothing prevents you from implementing a primitive menu-driven system with select. Listing 12.2 uses the select statement to offer the user a choice of application actions. The example continues to execute until the user chooses the Exit item. Then the
select statement and any shell script in which it might be contained is terminated with the exit shell built-in command.
PS3=Choice? select choice in "Enter Transactions" \ "Print trial balance" \ "Print invoices" \ "Exit" do case "$choice" in "Enter Transactions") . daily-trans ;; "Print trial balance") . trial-balance ;; "Print invoices") . invoices ;; "Exit") print "That's all, folks!"; exit ;; *) print -u2 "Wrong choice. Enter a number (1-4)." esac done
The Bourne shell supports a minimal amount of communication between processestypically, by way of the pipe operator. For example, you can invoke the ed line editor from a shell script to make a specific text change by using a command such as the
one shown below.
(echo "/^Payroll +1 i" cat newlist echo "." echo "w" echo "q" ) | ed - paylist
This form of intertask communication is sufficient if you need only to pass some data to another command or to read its output. Suppose, however, that in the Listing 12.4 you wanted to provide for the case that the file paylist doesn't contain a line
beginning with Payroll by skipping the insert, write, and quit editor commands. With the Bourne shell, you couldn't do this. With the Korn shell, you can maintain an interactive session with the ed command, with your program providing the instructions to
ed and responding to its output.
To use coprocessinga fancy term for the simultaneous execution of two procedures that read each other's outputyou first must launch the program with which you want to communicate as a background process, by using the special operator |&.
The |& operator is intended to suggest a combination of & (background execution) and | (the pipe operator). When the background command is started, its standard and standard output are assigned to pipes connected to your own processone for
writing to the command and one for reading the command's output.
The simplest way of sending a line to the coprocess is to use the print -p command. The -p option tells print to write to the coprocess's input pipe. To read output from the coprocess, use read p. Once again, the -p tells read to read from the coprocess
pipe.
Using these facilities, you could rewrite the preceding procedure like this:
ed paylist |& exec 3>&p exec 4<&p read -u4 # discard initial message line print -u3 P # Turn on prompting print -u3 "/^Payroll" # search for the insert location read -u3 # read prompt indicating success or failure case "$REPLY" in '*'*) # search must have been successful print -u3 i cat text >&3 # file containing data to be inserted print -u3 . read -u4 # read the ending prompt print -u3 w; read -u4 print -u3 q ;; *) # not found print -u3 q echo "invalid paylist file" exit 1 ;; esac done
You should note the following in this example: The exec command (exec 3>&p) is used to move the coprocess input pipe from its default location to a numbered file descriptor. The exec command (exec 4<&p) is used again to move the coprocess
output pipe to number file descriptor 4. Subsequent read and print commands specify the file descriptor as the source or sink of the operation, using the -u option. Ordinary UNIX commands can write to the coprocess by redirecting to file descriptor 3 (cat
filename >&3).
Admittedly, the program using coprocessing is more complicated than the earlier version, but it is also safer. The Bourne shell version would have added new lines after the first line if the search for Payroll failed. The Korn shell version fails
gracefully, without damaging the paylist file.
Notice that the Korn shell example of coprocessing in Listing 12.5 contains an incomplete cat command. This is because you need a special syntax to transcribe a file into the coprocess pipe. The standard Bourne shell syntax>filename and
>&fildesis inadequate. This is because >filename and >&fildes provide you with no way to reference the coprocess input and output pipes.
Actually, by using a Korn shell feature designed especially to support coprocessing, you can use I/O redirection to send output to or read input from the background process with any UNIX command. The technique required is to switch the default input and
output pipes created by the |& operator to explicit file descriptors. You use the exec command to do this:
exec 3>&p
When used with the exec command, this special form of the output redirection operator causes the pipe for writing to the coprocess to be assigned to file descriptor 3. (The lack of a command on the exec statement, of course, tips off the Korn shell that
you want to modify the current environment rather than execute another program.)
Similarly, the following code reassigns the pipe for reading from the coprocess:
exec 4<&p
If you place these lines at the front of the ed example, the cat command can be written in the familiar fashionby using I/O redirection to an open file descriptor. For example,
cat newlist >&3
Of course, the new syntax for the exec statement is a terrible kludge, amounting to a form of syntactic code that is difficult to remember. However, the basic outlines of coprocessing, including the |& operator and the -p options for print and read,
are straightforward enough, as is the underlying concept. Coprocessing is a powerful capability, making it possible to do things in a shell script that previously required the C programming language. So sharpen up your coding pencils, and try your hand at
coprocessing.
It almost might be said that the term shell refers to what you have before you customize itan empty shell. Of course, that's a gross exaggeration. The shell is more feature-laden than most programs you'll get an opportunity to shake a stick at.
Still, the Korn shell permits so much customization that it's no exaggeration to say you might find another user's login environment so foreign as to be almost unusable by you. Indeed, some places try to place a limit on user customization.
There are many ways to adapt the Korn shell to your preferred way of working. Of course, bear in mind that if you're a beginning UNIX user, you might not have many preferences to cater to. As your familiarity with UNIX and with the Korn shell increases,
you'll find many conveniences, shorthand methods, and customary usages that seem comfortable to you. The Korn shell helps you along by enabling you to encapsulate favorite behaviors into your login profile script and elsewhere.
Customizing the Korn shell begins with your login profile script, which is named .profile and which resides in your home directory. The file $HOME/.profile is of special importance because the Korn shell executes it every time you log inor, more
precisely, every time you launch an interactive shell.
Often the system administrator will place a starter .profile script in your home directory when he creates your login. Don't let yourself be cowed into thinking that there is anything sacrosanct in the hand-me-down .profile given to you. The contents of
your .profile script affect only you. It is specific to your login name and home directory. Altering it could conceivably affect only those people who have your password and can log in with your login name. Almost always, that is only you. Therefore, you
should feel free to add to, change, or delete anything in the .profile script, including deleting the whole file, if you want to. It doesn't matter to the shell. The .profile is supported only for your convenience; it isn't needed for Korn shell operation.
Your .profile script is, in fact, a shell script. Any shell programming techniques valid in a shell script are valid in the .profile script. If you're not a shell programmer, don't be daunted. Useful login profiles can be made up that contain nothing
more than straightforward UNIX and shell commands, without an if or while statement in sight. If you know how to use shell conditional and iterative statements, so much the better. Don't, however, think that mastery of them is essential to writing good
profile scripts. It isn't.
Your .profile script is an ideal place to put your favorite things. You might want to do the following things with your .profile. You should also observe the order in which the following are listed. Placing similar things together helps simplify the job
of maintaining your .profile.
Use the stty command to establish the control keys that you prefer to use. The default Erase key is #, and the default Kill key is @. Both are bad choices because their use as terminal control characters conflicts with their use as ordinary text
characters. You should redefine these keys with a statement similar to
stty erase '^H' kill '^U' intr '^C'
This example uses the caret (^) in front of an upper or lower case letter to designate a control key combination. Thus, erase '^H' specifies the Ctrl-h key combination as your backspace key. Of course, you would prefer to specify the actual characters
generated by your backspace key as the value for the erase characterif you can figure out what it is. The presence of a caret forces the use of quote marks. The caret is special to the shell; without quotes, it will cause improper interpretation of
the stty command. (For details about the stty command, refer to your UNIX user's reference manual.)
At the very least, you'll want to make sure that the variables PATH and MAIL have values. Usually, you'll want to set a great many more. If you use Bourne shell syntax, your variable settings will look like this:
PATH=/usr/bin:/usr/ucb:/usr/local/bin:$HOME/bin: MAIL=/var/spool/mail/$LOGNAME MAILCHECK=60 FCEDIT=/usr/bin/vi VISUAL=/usr/bin/vi export PATH MAIL MAILCHECK FCEDIT VISUAL
Alternatively, you can use the Korn shell export alias to avoid the need to remember to add each variable that you set to the export variable listit does little good to set a variable if you don't export it. Using the export alias, the previous
code would look like this:
export PATH=/usr/bin:/usr/ucb:/usr/local/bin:$HOME/bin: export MAIL=/var/spool/mail/$LOGNAME export MAILCHECK=60 export FCEDIT=/usr/bin/vi export VISUAL=/usr/bin/vi
When you write your environment variable settings, keep in mind that some are automatically set by the UNIX login processor. Your system administrator can also provide a login script to set values before your .profile script runs. For example, the PATH
and MAIL variables usually have initial values already set when your script starts. Overriding the default PATH variable is usually a good idea; you should have full control over your program search path, starting with its initial value. Overriding the
default MAIL or MAILPATH variable is risky, unless you know what mail subsystems are in use.
Local variables are variables that the shell uses but which don't be exported. They include FCEDITwhich designates the text editor to be used by the fc commandand the PS1 variablewhich is your primary prompt string. You might also want
to define a few local variables to hold the names of directories that you commonly access, which enables you to use cd $dir instead of the longer full pathname.
Define the aliases that you like to use. You must invent your own aliases; each user tends to have a different set. Most users, however, make up some aliases for the ls command. You can even redefine the default behavior of the ls command by defining an
alias named ls. Here are some typical aliases that I like to use:
alias lx='/usr/bin/ls -FC' alias l='/usr/bin/ls -l' alias pg='/usr/bin/pg -cns -p"Page %d:"' alias -t vi
Notice that in most cases I tend to use the full pathname for commands in the alias definition. I do this because it eliminates directory searches for the command, and it provides much the same effect as the Korn shell's alias tracking mechanism. Note
also the explicit use of the alias -t command to request the shell to track the vi command. The shell looks up the full pathname of the vi command and defines an alias named vi for me so that the plain command vi has all the performance but none of the
typing overhead of /usr/bin/vi.
Define any functions that you like to use, including autoload functions. I use some function definitions as keyboard shorthand because a function can do things that an alias can't. For example, you might want to use the go function, described earlier in
this chapter, for switching directories.
If you find yourself frequently setting the same shell options at the command line, you could set them in your .profile instead. To set the preferred shell options, use the set command. For example, if you prefer to use the vi mode for command history
and editing, and you want full job control support, you might add these two lines to your .profile:
set -o vi set -o monitor
Execute commands that you like to run every time you login. For example, you might want to run the who command to find out who's currently logged in. Likewise, the df, which isn't present on all UNIX systems, displays the amount of free disk space
available on mounted filesystems.
Whenever you change your .profile script, you should execute it before you log out. If you make an error in your script, you might have difficulty logging back in. To test your .profile script, you can run it with the . (dot) command:
$ . ./.profile
Be sure to leave a space after the first period: it's the command name, and ./.profile is the command argument. (Although .profile will usually be adequate by itself, you might need to use ./.profile if your current directory is not in the search path.)
The dot command not only executes the script but also leaves any environment changes in effect after the script terminates.
Alternatively, you can run the script with ksh -v to have the shell execute the script and print each statement as it is executed:
$ ksh -v ./.profile
Using the -n option would cause the Korn shell to read your .profile and check it for syntax errors, but not execute the commands it contains.
After you have your .profile set up the way you want, you're ready to tackle the environment file. The environment file is any file that contains shell scripts that you designate by assigning its pathname to the ENV variable. The shell automatically
executes the ENV file whenever you start a new invocation of the shell, and when it executes a command. If you've ever shelled out from commands like pg and vi, you know that when you call the shell again, some environment settings, such as aliases, aren't
carried over from your login shell. By placing aliases, function definitions, and even global variable settings in a separate file and setting ENV to its pathname in your .profile script, you can ensure that you have a consistent Korn shell environment at
all times.
Don't get carried away, though. In some cases, the file designated by the pathname value of ENV is executed in front of shell commands that you call. Because many UNIX commands are implemented as shell scripts, this means that a large environment file
can add surprising overhead to some unexpected places.
To use an environment file create a file that contains the aliases, functions, and exported variable settings that you prefer. Then add the statement export ENV=pathname, where pathname is the full pathname of your environment file, to
your .profile. The environment file will become effective the next time you log in. It will become effective immediately if you test your .profile with the following . command:
. .profile
Customizing your environment doesn't stop with using the login profile and environment file to establish shell options and settings you want; it's also a handy place to put settings used by other programs. For example, one way to customize your vi
editing environment is by defining a variable EXINIT that contains the commands vi will run every time you start it. You could place the EXINIT variable setting in your login profile to establish your preferred vi settings. Many UNIX commands respond to
environment variables, which enables you to customize these commands in your login profile.
The idea of a job may be somewhat foreign to UNIX users, for in UNIX most of the action is interactive. Nevertheless, even the Bourne shell provides basic tools for running background jobs, and UNIX the operating system has always provided such tools.
The more recent releases of UNIX have even enhanced background job management.
The basic idea of a background job is simple. It's a program that can run without prompts or other manual interaction and can run in parallel with other active processes. With the Bourne shell, you launch a background job with the & operator. For
example, the command cc myprog.c & compiles the source program myprog.c without tying up the terminal. You can do other work, even edit files with a full-screen editor, while the cc command works behind the scenes.
Enhancements to the stty command and the terminal driver in recent UNIX releases have added a new control key to your terminal: Suspend. Suspend is usually Ctrl-z. This new tool enables you to take an interactive program that you're currently running,
such as a vi editing session, and to put it temporarily into the background. If the program wants to talk to your terminal, the system suspends the program. Otherwise, it continues running.
The Korn shell adds some tools that help you manage the family of processes you can accumulate. These tools consist of the jobs, kill, wait, bg, and fg commands.
To use the Korn shell's job control tools, you must have the monitor option enabled. Normally, the monitor option is enabled for you automatically; it's the default for interactive shells. If your operating system doesn't support job management, the
default for the monitor option is off. Even without operating system supportthe Suspend key and stty function is an operating system service, not a Korn shell serviceyou can still use some of the Korn shell's job control tools, but you must set
the monitor option on yourself. You do that with the command set -o monitor.
The jobs command, which takes no arguments, simply lists the jobs that you currently have active. The output of jobs looks like this:
$ jobs [1] + Running xlogo& [2] + Running xclock -bg LightGreen& [3] + Stopped vi myprog.c
You use the kill, bg, and fg commands to manage jobs. When referring to a job, you use the job number shown in brackets in the output of jobs, preceded by a percent (%) sign. For example, kill %1 would terminate the xlogo program that you currently have
running. The wait, kill, bg, and fg commands can also refer to background jobs by their process ID, which you can generally obtain from the output of the ps command. However, the use of Korn shell job numbers is preferred, because they are simpler and
safer to use than process IDs. Refer to Chapters 18 and 19 for more details on processes.
You create jobs in one of three ways by explicitly designating a command for background execution with the & operator; by switching a job into the background with the Korn shell bg command; or by pressing the Suspend keyusually
Ctrl-zwhile a foreground program is running.
By convention, a job started or switched into the background continues to run until it tries to read from your terminal. Then it is suspended by the operating system until you intervene. When it is in this state, the jobs command shows that the command
is Stopped.
A job that has been stopped usually needs to talk to you before it can continue. In the previous jobs example, the vi command is shown to be stopped. The command won't continue until you reconnect it to your terminal. You do this with the fg
commandfor example, fg %3 or fg %vi. The vi command then becomes the foreground process, and it resumes normal interactive execution with you.
The full syntax of the % argument accepted by the wait, kill, fg, and bg commands is shown in Table 12.6.
Syntax |
Meaning |
%number |
References job number |
%string |
References the job whose command begins with string |
%?string |
References the job whose command contains string |
%% |
The current job |
%+ |
The current job (also %%) |
%- |
The previous job |
The syntax of the Korn shell job control commands are summarized below.
Use the jobs command to display background jobs and their status. For example,
jobs [ -lp ] [ job ... ]
The -l option causes the jobs command to list the process ID for each job in addition to its job number. The -p option causes the jobs command to list only the process ID for each job instead of its job number.
If you omit the job arguments, jobs displays information about all background jobs, as in this example:
$ jobs [1] + Running xlogo& [2] + Running xclock -bg LightGreen& [3] + Stopped vi myprog.c
If you include job arguments, it displays information only for the specified jobs. For job, specify a process ID or a job reference beginning with %. For instance, to find out whether job 2 from the previous example is still running, you
would enter this command:
$ jobs %2 [2] + Running xclock -bg LightGreen&
Use the kill command to send a signal to the specified jobs. Some signals cause a job to terminate. The TERM signalalso called signal 15, or interruptusually causes a job to terminate gracefully, whereas signal 9 always terminates a job but
may leave files unclosed or wreak other havoc on the job that was in progress. You should use kill -9 only when you cannot terminate the job any other way.
The kill command is normally a UNIX system command, but the Korn shell provides kill as a built-in command with enhanced capabilities. The Korn shell supports the basic functionality of the UNIX kill command transparently. Its syntax is
kill [ -signal ] job ...
For signal specify a signal number or a signal name. Signal numbers 1 through 15 are always valid. A signal name is one of a predefined list of mnemonic symbols that correspond to the valid signal numbers. Use kill -l to obtain a list of the
valid signal names. The names TERM (terminate) and HUP (hang-up) are always valid. (Refer to your UNIX user's reference manual for more information about the kill and signal commands.)
For job, provide one or more process ID numbers or job references. Job references begin with %. You must provide at least one job argument with the kill command.
By way of example, suppose you have started an xclock process, displaying a clock on your X terminal screen:
$ xclock -bg LightGreen& [4] + Running xclock -bg LightGreen&
You can cancel the xclock window (a background job) with either of the following commands:
$ kill %4
or
$ kill %xclock
Use wait to suspend the shell until the specified job, if any, finishes. The visible effect of wait is simply to cause the shell not to issue another prompt to you. To get the prompt back if you decide not to wait, simply press Enter. This causes the
shell to issue a prompt, and it terminates the wait command. The syntax of the wait command is
wait [ job ... ]
For job, specify one or more process ID numbers or job references that designate the job or jobs you want to wait for. If you specify no jobs, the shell waits until any job finishes. If you specify two or more jobs, the shell waits until all the
specified jobs finish.
You won't use the wait command too often, but it is convenient when you have done all the interactive work you have and need the results of one or more background jobs before you continue. Without the wait command, you would have to execute the jobs
command repetitively until the job or jobs that you wanted were marked Done.
One situation where the wait command comes in useful is when developing some formatted text files. You may want to run nroff or troff as background jobs, capturing the output to a disk file for review. While the nroff or troff job is running, you can
edit other text files. However, when you have no other editing work to do, you'll need to wait for nroff or troff to finish because you have nothing else to do but review your previous work. A hypothetical console session might look like this:
$ vi chap1.nr $ nroff -me chap1.nr >chap1.nrf & [4] + Running nroff -me chap1.nr $ vi chap2.nr $ nroff -me chap2.nr > chap2.nrf & [5] Running nroff -me chap2.nr $ jobs [4] Running nroff -me chap1.nr [5] Running nroff -me chap2.nr $ wait
In this example, you overlapped editing of chap2.nr with formatted printing of chap1.nr. However, after finishing the edit of chap2.nr, you see by running the jobs command that both nroff jobs are still running. Since you have no more editing tasks to
perform, you can use the wait command to wait until one of the two background jobs finishes. The shell will not issue another prompt until one of the two jobs is done, then you'll receive a Done message:
$ wait [5] Done nroff -me chap2.nr $
Use fg to move background jobs into the foreground. Foreground execution implies interactive processing in connection with the terminal. Therefore, using fg to bring more than one job into the foreground establishes a race condition. The first job to
get your terminal wins, and the others revert to Stopped status in the background. The syntax for fg is
fg [ job ... ]
For job, specify one or more process ID numbers or job references. If you omit job, the current background process is brought into the foreground. The current job is the job that you most recently stopped or started.
The need to use the fg command often arises as a result of actions you take yourself. For example, suppose you are editing a text file with vi and, when trying to save the file and quit, you discover that you do not have write permission for the file.
You can't save the file until you correct the condition, but you're currently stuck inside the editor. What do you do?
First, stop the vi editor session by pressing Ctrl-z. You'll immediately get the following console output:
[1] Stopped vi chap2.nr $
Now, determine the cause of the problem and correct it. For the sake of brevity, we'll assume that the problem is nothing more than that you've tried to edit a file you've write-protected:
$ ls -l chap2.nr -rrr 1 barbara user 21506 May 5 10:52 $ chmod u+w chap2.nr $ ls -l chap2.nr -rw-rr 1 barbara user 21506 May 5 10:52
Finally, use the fg command to bring the vi edit session, currently stopped in background, back into execution:
$ fg %vi
You might need to type Ctrl-l (a vi editor command) to redraw the screen.
Use the bg command to place jobs currently in the Stopped status (as indicated by the jobs command) into the background and to resume execution. Note that a job will immediately switch back into the Stopped state if it requires terminal input. The
syntax for bg is
bg [ job ... ]
For job, specify one or more process ID numbers or job references. A job reference begins with %. If you omit job, the command refers to the current job, which is the job that you most recently started or stopped.
In actual practice, you don't use the bg command to move a foreground job into the background, because there's no way to do so: the shell is not listening to your terminal while a foreground job is running. To get the shell's attention while a
foreground command is running, you'll need to use Ctrl-z to stop (suspend) the foreground job.
Once you've stopped the job and have a shell prompt, you'll need to decide what to do with the job you stopped. You can perform other tasks, and when finished restart the stopped job with the fg command, as described earlier. But if the job you stopped
is not interactive, that is, if it can run without constant input from you, then you can tell the shell to restart the job but leave it in the background.
As an example, suppose you've started a long-running format of a text file using the troff command:
$ troff -me chap1.nr > chap1.trf
If, after waiting a few minutes for the job to finish, you find that you want to do something else instead of just sitting there, you can use the following sequence to switch the troff command to background execution:
[ctrl-z] $ bg $
By default, the shell assumes you mean the job you last stopped. Now that the troff command is running in the background, you can do other work.
The net result of these actions is the same as if you had started the troff job in the background to begin with:
$ troff -me chap1.nr > chap1.trf &
This chapter presented the features of the Korn shell. Because the Korn shell has many features in common with Bourne Shell, only the features special to the Korn shell were discussed in this chapter.
The Korn shell is one of several shells available to you on most contemporary versions of the UNIX operating system. It is a newer, enhanced version of the original Bourne shell, with command history, command editing, command aliases, and job control to
improve your keyboard productivity. It also offers a number of improvements for the shell script writer, including arithmetic variables and arithmetic expressions, array variables, a select statement for prompting the user with menus, and a coprocess
mechanism for interactively executing other UNIX commands from within a shell script.
The initial impetus for construction of the Korn shell was to bring many of the enhancements in csh to users in a format consistent with the Bourne shell syntax and behavior. The C shell (csh) was implemented by the Berkeley group and was initially
offered only in the BSD variant of UNIX. The Korn shell ported its extensions, together with many additional improvements, into the System V environment. Many people feel that the Korn shell is a successor to both the Bourne and C shells. It is now the
shell of choice for use at the keyboard and for writing shell scripts.
The command history feature provides for capturing in a disk file each command as you execute it. The file is preserved across logins so that you have some of the context of your previous session when you next log in. You can use the command history
file for reference or for reexecuting commands. When you reexecute a command, you can use it as it was originally written or you can modify it before execution. The fc command and the history and r aliases provide the user interface to the command history
file.
The command editing feature provides two different text editor styles for editing commands as you write them. You must explicitly enable command editing to use it. By default the Korn shell manages the command line in the same way as the Bourne shell
does. The vi edit mode implements most of the vi input and command modes, and it enables you to access and reuse commands stored in the command history file. The EMACS edit mode is compatible with the EMACS editor commands. Most users find either the vi or
EMACS command editing mode to be more natural than the equivalent bang (!) notation of the C shell.
The command alias feature enables you to define new command names that stand for a leading portion of the command line of existing commands. The definition of an alias can replace not only the name of an existing command but also initial options and
arguments of the command line. This greatly reduces the amount of typing needed for frequently executed commands. The feature also replaces the command tracking feature of the Bourne shell.
Extensions to wildcard file naming patterns provide more complex expressions that you can use to narrow in on the specific files you want to reference.
Features added for the benefit of the script writer are numerous and powerful. They eliminate some of the kludges that you used to have to deal with when writing new commands.
The typeset command provides a host of new features surrounding the use of shell variables. Array variables with the form ${name[n]} permit convenient processing of lists. Integer variables defined with typeset, the let command, and the (( ))
expression notation enable you to do basic numeric calculations without having to leave the shell environment. You no longer have to resort to command substitution for the expr or bc commands.
An improved syntax for command substitution makes even this chore more palatable. The syntax $(...) for command replacement reduces the need for quoting substrings inside backquoted expressions. You can even nest them, which permits expressions such as
$(...$(...)...) on the command line.
Coprocessing, a new feature of the shell, enables you to read and write from background commands, using them in interactive fashion. You can respond to error messages produced by the invoked command, and you can provide a programmed response. You launch
a coprocess with the |& operator, using it in place of the & symbol. Once launched, a coprocess runs in parallel with your shell's process. To write to the command, use print -p. To read its output, use read -p. You can reassign the input and
output pipes by using the exec fd>&p and exec fd<&p special commands. Now the script writer can do things previously possible only in the C programming language.
Another boon is the privileged shell mode. You can set the set-uid and set-gid flags on your shell scripts. You can use the set -o privileged or set -p option to toggle between the user's real user ID and the effective user ID. Use this feature to write
special system servicesfor example, a tape library management system, a device allocation facility, or a file sharing system.
Last but not least, the Korn shell provides a way of getting around the problem of not being able to export aliases and functions. Using the ENV exported variable, you can define a miniprofile to be executed at each invocation of the shell. You no
longer have to switch to the shell from vi, pg, or sdb only to find a bare-bones environment without your favorite aliases and functions.
All in all, the Korn shell seems to be just about the final word in command-line environments. Now your main concern will be whether compatibility constraints enable you to use the Korn shell for script writing. Although the Korn shell can execute Bourne shell scripts, the Bourne shell can't execute Korn shell scripts, and only the C shell can execute C shell scripts. At least you're free to use it for your keyboard environment, which is a step up for sure!