Полезная информация

Previous Page TOC Next Page Home

  • Shell Programming
  • Customizing
  • Summary
  • Section 3

    Variable Arithmetic

    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 declare—or set aside—some variables for the special purpose of doing arithmetic. The let command is where all this magic really happens.

    Using typeset

    The Korn shell is still a very slow tool for doing repetitive calculations, even with the typeset statement. Floating-point—real numbers with decimal points and fractions and the like—isn'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 exported—made available—to 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 variables—usually by way of the export alias—the 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 typeset—to set up output formatting options for variables—is 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.

    Using let

    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 number—for 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 minus—the 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 true—that is, causes execution of the then clause—and a command setting a non-zero exit value is false—that 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 expression—for example, let sum=x+y—to 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 value—namely, 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 statement—but only when the variable name being assigned was previously defined as an integer variable with the typeset or integer statement.

    Some Practical Examples of Arithmetic

    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 calculation—written to standard output—isn'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 way—that is, converting from hexadecimal to decimal—is 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

    Shell Programming

    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.

    Conditional Expressions

    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 fildes—whose default is 1—is 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.

    Functions

    The Korn shell fully supports Bourne shell functions. It also provides some extensions.

    Defining Functions

    In addition to the Bourne shell syntax, the Korn shell supports the following alternate syntax for defining a function:

    function identifier
    
    {
    
         command-list
    
    }
    Using Variables in Functions

    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
    
    }
    Using Traps in Functions

    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:


    typeset -f


    Lists the functions currently defined and their definitions. The predefined alias functions does the same thing.


    typeset -ft name ...


    Activates the xtrace option whenever function name is invoked. Tracing reverts to its former state when the function returns.


    typeset -fx name ...


    Defines functions as exported. Exported functions are inherited by shell scripts. However, a function cannot be exported to another instance of ksh. There is no method for passing function definitions through the command environment, as there is for variables.


    typeset -fu name ...


    Defines functions for autoload. A call to an autoload function before its definition is recognized as a function call when the function has been declared with typeset. The Korn shell searches the directories named in the FPATH variable for a file that has the same name as the function. If the Korn shell finds such a file, the function is loaded and executed, and the definition is retained as though an inline definition of the function had been read at that point.

    Using Autoload Functions

    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 searched—in the same manner as you set the PATH environment variable—and 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.

    Undefining Functions

    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.

    When To Use Functions

    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.

    Scanning Arguments with getopts

    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 string—indicated 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 on—1, 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.

    Using the select Statement

    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 process—from printing a selection menu to prompting for the user's choice to reading it.

    In fact, because the user might choose an illegal option—requiring you to repeat the menu selection process—or 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 line—that is, presses Enter or Return without typing anything—select 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 loop—in 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

    Using Coprocesses

    The Bourne shell supports a minimal amount of communication between processes—typically, 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 coprocessing—a fancy term for the simultaneous execution of two procedures that read each other's output—you 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 process—one 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).


    NOTE: Use read -p or print -p to read from or write to the coprocess until you have moved the coprocess input or output to a number file descriptor; then read or write to that file descriptor: read -u4 or print -u3.

    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 >&fildes—is 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 fashion—by 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.

    Customizing

    It almost might be said that the term shell refers to what you have before you customize it—an 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 in—or, 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.

    Setting Control Keys with stty

    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 character—if 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.)

    Setting Environment Variables

    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 list—it 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.

    Setting Local Variables for Shell Control

    Local variables are variables that the shell uses but which don't be exported. They include FCEDIT—which designates the text editor to be used by the fc command—and the PS1 variable—which 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.

    Defining Aliases

    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.

    Defining Functions

    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.

    Setting Shell Options

    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

    Executing Commands Every Time You Login

    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.

    Executing Your .profile After Changing It

    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.

    Creating an ENV File

    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.


    NOTE: As a rule, the environment file is executed as a preliminary step to invoking a shell script only when the shell script requires a new invocation of the Korn shell. This usually isn't the case when you invoke a shell script by its name.

    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

    Adding Settings for Other Programs to Your .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.

    Job Control

    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 support—the Suspend key and stty function is an operating system service, not a Korn shell service—you 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 key—usually Ctrl-z—while 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 command—for example, fg %3 or fg %vi. The vi command then becomes the foreground process, and it resumes normal interactive execution with you.


    NOTE: A full-screen program, such as vi, probably won't recognize that the screen no longer matches your last edit screen. You probably will need to press Ctrl-l to redraw the screen before you resume your edit session. Other programs that merely need your response to a prompt don't require any special action when you resume them with fg.

    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.

    Displaying Background Jobs and Their Status

    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&

    Sending Signals to a Job

    Use the kill command to send a signal to the specified jobs. Some signals cause a job to terminate. The TERM signal—also called signal 15, or interrupt—usually 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.)


    NOTE: The reason for the vagueness about signal names is that they vary from one version of UNIX to another. You'll have to use kill -l to find out the names that pertain specifically to your system.

    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

    Suspending the Shell Until a Job Finishes

    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
    
    $

    Moving Background Jobs into the Foreground

    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
    
    -r—r—r—   1  barbara   user     21506 May 5 10:52
    
    $ chmod u+w chap2.nr
    
    $ ls -l chap2.nr
    
    -rw-r—r—   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.

    Moving Foreground Jobs into the Background

    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 &

    Summary

    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 services—for 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!

    Previous Page TOC Next Page Home