TOC BACK FORWARD HOME

UNIX Unleashed, System Administrator's Edition

- 11 -

Korn Shell

by John Valley and Chris Johnson

Chapter 8, "What Is a Shell," introduced the basics of UNIX shells, and Chapter 9, "The Bourne Shell," discussed the Bourne shell in particular. This chapter expands on the subject of shells by introducing the Korn shell--the second of the three main shell languages available to you. The third major shell language is discussed in Chapter 12, "The C Shell."

The Korn shell is named after its author, David G. Korn of AT&T's Bell Laboratories, who wrote the first version of the program in 1986. Therefore, the Korn shell is a direct descendent of the Bourne shell. It is almost perfectly compatible with the Bourne shell; with a few minor exceptions, any shell script written to be executed by the Bourne shell can be executed correctly by the Korn shell. As a general rule, though, Korn shell scripts cannot be processed correctly by the Bourne shell.

This upward compatibility provides a number of advantages--not the least of which is that it enables you to capitalize on your knowledge of the Bourne shell immediately. It also drastically reduces the amount of material you need to learn to begin using the Korn shell.

Because the Korn shell is intended as a replacement for and an improvement on the Bourne shell, it is best discussed as a series of features added to the basic functionality of the Bourne shell. Many aspects of the shell's operation presented in Chapter 9, "The Bourne Shell," are not repeated here. Instead, this chapter summarizes the differences between the Bourne shell and the Korn shell.

The list of Korn shell enhancements is extensive, ranging from the profound to the trivial. The most dramatic enhancements are those intended to facilitate keyboard interaction with the shell, but you also should be aware of many important extensions to shell syntax and shell programming techniques. The categories of enhancements follow:

Although you haven't been introduced to the C shell yet, you'll find that many of the Korn shell features duplicate those of the C shell, but with a different syntax. This is intentional. Although the C shell offers many desirable features, its general syntax is incompatible with the Bourne shell, making it somewhat of a square peg in a round hole in the UNIX world. The Korn shell solves this long-standing quandary in the UNIX world by offering the keyboard and shell programming features that people want, but in a form that is compatible with the old, well-established Bourne shell standard.

Shell Basics

As I mentioned earlier, the Korn shell is essentially a foundation equivalent to the Bourne shell with a new layer of goodies added on top. You can use the Korn shell as a one-for-one replacement of the Bourne shell, with no special knowledge of Korn shell features. Korn shell extensions do not come into play until you explicitly invoke them.

In particular, the Korn shell is identical to the Bourne shell in the following areas:

$ cat <<-!
     This is a demonstration of a here document. As you can see the
     docuemnt uses the operator << to tell the shell that all the text on the next
     line to the label, in this case is !, is all to be read in and redirected to
     the cat command. This - tells the shell to remove leading tabs at the start
     of the line.
!
This is a demonstration of a here document. As you can see the
docuemnt uses the operator << to tell the shell that all the text on the next
line to the label, in this case is !, is all to be read in and redirected to
the cat command. This - tells the shell to remove leading tabs at the start

The general philosophy of the Korn shell is to invoke extensions and special features with syntax that is not legal for the Bourne shell. As a result, any commands and shell scripts that are syntactically correct for the Bourne shell will be interpreted identically by the Korn shell. All Korn shell extensions use syntactic forms that do not appear in the Bourne shell language.

Features that are not invoked directly by commands, such as command history and command editing, are controlled instead by shell options. To use command editing, you first must issue the command set -o vi or set -o emacs. If you don't, the Korn shell command line works the same as the Bourne shell. Also note that the set command follows the general philosophy: set -o is not valid in the Bourne shell and generates a syntax error.

The compatibility between the Bourne shell and Korn shell is nearly perfect; one of the design objectives of the Korn shell was that it be able to execute system-provided shell scripts written for the Bourne shell without the need to revise those scripts or to invoke the Bourne shell to run them. This objective meant that even minor idiosyncrasies of Bourne shell behavior could not be overlooked; the Korn shell design had to implement them all.

The upshot of all this is that everything in Chapter 9 applies equally well, without restriction or caveat, to the Korn shell.

Wildcard Expressions

The Bourne shell supports a number of syntactic forms for abbreviating a command-line reference to filenames. These forms are based on the idea of embedding one or more special pattern-matching characters in a word. The word then becomes a template for filenames and is replaced by all the filenames that match the template. The pattern-matching characters supported by the Bourne shell are *, ?, and the bracketed expression [...].

These pattern-matching characters are supported by the Korn shell, as well as a tilde expansion that uses the ~ character to shorten pathnames and the extended pattern-matching expressions, *(), ?(), +(), @(), and !(). The syntax of pattern-matching expressions is based on the recognition of unquoted parentheses--()--in a word. Parentheses are special to the shell in both the Bourne and Korn shells; they must be quoted to avoid their special meaning. The Bourne shell attaches no special significance to a word such as here+(by|with), but it would complain about the parentheses. Thus, words containing embedded parentheses do not occur in the Bourne shell. The Korn shell therefore uses this syntax to extend wildcard pattern-matching without impairing Bourne shell compatibility.

Tilde Expansion A word beginning with ~ (the tilde) is treated specially by the Korn shell. To avoid its special meaning, you must quote the tilde. Note that words containing a tilde in any position except for the first are treated normally. The tilde has a special meaning only when it appears as the first character of a word.

Table 11.1 lists the four styles of tilde expansion.

Table 11.1. Tilde expansion styles.

Style Description Example
~ When used by itself or followed by a slash (/), the tilde is replaced by the pathname of your home directory. It is the same as writing $HOME or $HOME/.... $ echo

~/usr/home/fran

$ echo ~/bin

/usr/home/fran/bin
~string A tilde followed by an alphanumeric string is replaced by the home directory of the named user. It is an error if no entry exists in the /etc/passwd file for string. $ echo ~bill

/usr/home/bill
~+ A tilde followed by a plus sign is replaced by the full pathname of the current directory. It is the same as writing $PWD or $PWD/.... $ pwd

/usr/lib

$ echo ~+/bin

/usr/lib/bin
~- A tilde followed by a minus sign is replaced by the full pathname of the previous directory. It is the same as writing $OLDPWD or $OLDPWD/.... $ pwd

/usr/lib

$ cd ~/lib

/usr/home/fran/lib

$ echo ~-/bin

/usr/lib/bin

As you can see, the tilde shorthand is a great time saver.

Pattern Expressions A pattern expression is any word consisting of ordinary characters and one or more shell pattern-matching characters. The pattern-matching characters are the familiar *, ?, and [...] from the Bourne shell, as well as any of the extended pattern-matching expressions shown in Table 11.2.

Table 11.2. Extended pattern-matching expressions.

Expression Description
*(pattern[|pattern]...) Matches zero or more occurrences of the specified patterns. For example, time*(.x|.y) matches the filenames time, time.x, time.y, time.x.x, time.y.y, time.x.y, and time.y.x, but it doesn't match the filename time.z.
+(pattern[|pattern]...) Matches one or more occurrences of the specified patterns. For example, time+(.x|.y) matches time.x, time.x.x, time.y, time.x.y, and so on, but it doesn't match time.
?(pattern[|pattern]...) Matches any one of the patterns. It won't concatenate or repeat patterns to match files, unlike *(pattern). For example, time?(.x|.y) only matches time, time.x, and time.y, but it doesn't match time.x.x.
@(pattern[|pattern]...) Matches exactly one occurrence of the pattern. For example, time@(.x|.y) matches time.x or time.y, but it doesn't match time, time.x.x, or time.x.y.
!(pattern[|pattern]...) Same as *, except that strings that would match the specified patterns are not considered matches. For example, time!(.x|.y) matches time, time.x.y, time.0, and everything beginning with time except for time.x and time.y.


CAUTION: You'll notice that the expressions *(pattern[|pattern]...) and +(pattern[|pattern]...) will match any combination of the specified pattern. This can be both useful and dangerous. If in doubt, use echo to find out what files the patterns will match. You won't be popular if you end up removing the system configuration by mistake!

Note that the definition of pattern expressions is recursive. Each form contains one or more pattern strings. This means that nested pattern expressions are legal. Consider, for example, time*(.[cho]|.sh). It contains the pattern [cho] inside the pattern expression, which causes it to match time.sh, time.c, time.h, time.o, time.sh.c, time.c.o, and so on. The pattern time*(.*(sh|obj)) matches the filename time.sh or time.obj.

The main value of these extended pattern-matching expressions is in enabling you to select a subset of files without having to list each filename explicitly on the command line. Pattern expressions also are legal in other contexts where the shell does pattern matching, such as in the expression of the case statement.

Command Substitution

Another noteworthy enhancement provided by the Korn shell is a more convenient syntax for command substitutions. Remember from Chapter 10, "The Bourne Again Shell," that a string quoted with backquotes (´command´) is replaced with the standard output of command. The backquote notation isn't easy to use, though. The Korn shell supports the following alternative form in addition to the standard Bourne shell backquote notation:

$(command-list)

where command-list is any valid list of commands. In its simplest form, a command list is a list of commands separated by semi-colons. Not only does the parenthesized form avoid the problem of recognizing backquotes on printed listings, but it also acts as a form of quoting or bracketing. You can use all the standard quoting forms inside the parentheses without having to use backslashes to escape quotes. Furthermore, the parenthesized form nests; you can use $() expressions inside $() expressions without difficulty.

For example 'ls' can be replaced with $(ls). Similarly, 'ls;who' can be replaced with $(ls;who).

An Improved cd Command

For directory movement, the Korn shell supports two new forms of the cd command:

cd -
cd oldname newname

The command cd - is especially helpful. It switches back to the directory you were in before your last cd command. This command makes it easy for you to switch to another directory temporarily and then move back to your working directory by typing cd -. The PWD and OLDPWD variables are maintained to carry the full pathnames of your current and previous directory, respectively. You can use these variables for writing commands to reference files in a directory without typing the full pathname.

You can use the cd oldname newname command to change a component of the pathname of your current directory. This makes lateral moves in a directory structure somewhat easier. Suppose that your current directory is /usr/prod/bin and you want to switch to the directory /usr/test/bin. Just type the command cd prod test. Similarly, the command cd usr jjv switches from /usr/prod/bin to /jjv/prod/bin, assuming that the latter directory exists.

Aliases

The command-aliasing feature of the Korn shell is certainly one of its most attractive and flexible enhancements over the Bourne shell. It's an enhancement you'll start using right away.

When you define a command alias, you specify a shorthand term to represent a command string. When you type the shorthand term, it is replaced during command execution with the string it represents. The command string can be more than just a command name; it can define options and arguments for the command as well.

You might have one or more preferred ways of listing your directory contents, for example. I like to use the -FC options on my ls command when I just want to see what's in the directory. Typing the command ls -FC ... repeatedly all day long, though, would not be one of my favorite things to do. The command-alias feature makes it easy to set up a shorthand for the ls command. You do it like this:

$ alias lx=`ls -FC`

Now, whenever you enter lx on the command line, the command ls -FC is executed.

Defining Aliases

The alias command is a built-in shell, meaning that it is available to you only when running the Korn shell. It is not part of the UNIX operating system at large. You use the alias command to define new aliases and to list the command aliases currently in effect.

The general syntax of the alias command follows:

alias [ -tx ] [ name[=value] ... ]

The arguments of alias are one or more specifications, each beginning with an alias name. The alias name is the shorthand command you enter at the terminal. After the equal sign (=), you enter the text with which you want the shell to replace your shorthand. You should enclose the alias value string in single quotes to hide embedded blanks and special characters from immediate interpretation by the shell. The -t and -x arguments enable you to manipulate the alias command in different ways. Specifying -t enables you to see all the tracked aliases, and using -x enables you to define an alias as exportable--much in the same way variables are exportable if you use the export command. For more details on these options, see "Using Tracked Aliases" and "Using Exported Aliases" later on in this chapter.

The Korn shell stores alias names and their definitions in an internal table kept in memory. Because the table is not stored in a disk file, you lose your alias definitions whenever you log out or exit the Korn shell. To keep an alias from session to session, you need to define the alias in your logon profile--a file in your home directory named .profile. There's nothing tricky about it. The same command you enter at the keyboard to define an alias works just as well when issued from a logon profile script. Thus, for aliases you want to use over and over, simply type the alias command in your logon profile; you only have to do it once. (For more information about using the logon profile, see "Customizing the Korn Shell," later in this chapter.)

The syntax of the alias command enables you to define more than one alias on a command. The general syntax follows:

alias name=value [name=value]...

You don't usually write multiple definitions on one alias command, because you usually think them up one at a time. In your logon profile, it's a good idea to write only one alias definition per alias command. This makes it easier to add and delete alias definitions later.

After you define an alias, you might want to list the aliases in effect to see your new definition. Simply enter the alias command with no arguments, as in this example:

$ alias
true=let
false=let
lx=ls -FC

In all likelihood, there are a good many more alias definitions in effect than you defined. The Korn shell automatically defines a number of aliases when it starts up, such as when you log on, to provide convenient abbreviations for some Korn shell commands. The true and false definitions fall into this category. The UNIX operating system provides true and false commands, but, as programs, they must be searched for and loaded into memory to execute. As aliases, the shell can execute these commands much more quickly, so these two particular aliases are provided as an easy performance enhancement for the many shell scripts you execute, usually unknowingly, throughout the day.

To use the lx command alias shown in the last example, use it as a new command name, as in this example:

$ lx

This, by itself, lists all the files in the current directory in a neat, columnar format sorted for easy inspection. To list a directory other than the current directory, use this command:

$ lx /usr/bin

After alias substitution, the shell sees the command ls -FC /usr/bin.

The capability to prespecify command options in an alias is a great help. Even better, you usually can augment or alter prespecified command options when you use the alias. Suppose that you want to add the command option -a when listing /usr/bin so that you can see all dot files in the directory. You might think that you have to type the full ls command, because the lx alias doesn't include an -a option letter. This is not so. The following command works quite well:

$ lx -a /usr/bin

When the shell executes this command, it immediately replaces lx with the alias value string, obtaining the following internal form:

$ ls -FC -a /usr/bin

The ls command, like most other UNIX commands, is comfortable with command options specified in multiple words. In effect, the -a option has been added to the -FC options provided automatically by the alias.

Removing an Alias

To remove an alias that you or the Korn shell defined previously, use the unalias command:

$ unalias name [ name ... ]

Notice that, just as you can define multiple aliases on one command line, you also can remove multiple aliases with one unalias command.

Writing an Alias Definition

One of my favorite aliases is the following one for the pg command:

$ alias pg=`/usr/bin/pg -cns -p"Page %d:"`

The pg alias is instructive in a number of ways. Take a look at it in detail.

First, note that the alias name is pg. This is the same as the pg command itself, so, in effect, the alias hides the pg command. You can invoke the real UNIX pg command by using an explicit pathname--calling /usr/bin/pg--but not by the short command pg, which invokes the alias instead.

Choosing the same name for an alias as a real command name is unusual. It implies that you never want to execute the real command directly and that you always want to dress it up with the options specified in the alias.

Because of the way I work, the options -c, -n, -s, and -p should have been built into the pg command; I always want to use them. The -c option causes pg to clear the screen when it displays a new page. On a video terminal, this is more natural and faster than scrolling the lines. The -n option causes pg to execute a command key immediately without waiting for the Enter key to be pressed. All pg commands consist of only one letter. The only reason not to use the -n option is to avoid the slack in performance that results from generating a terminal interrupt for each keypress, which the -n option requires. Single-user workstations and modern high-performance computers don't notice the extra workload, however. Therefore, unless you're working on an old PDP-11, go ahead and specify the -n option for the convenience it adds. The -s option displays messages, such as the current page number, in highlighted mode and usually in inverse video, which makes the non-text part of the display easier to notice or ignore.

The -p option causes the pg command to display the page number at the bottom of each screen. I like page numbering because it gives me a rough idea of where I am in the displayed document. By default, the page number is displayed as a bare number, run on with the rest of the command line. The pg command, however, enables you supply a format for the page number. I specified -p"Page %d:". It identifies the page number with the word Page and provides a colon (:) to separate the page number from the input command line.

Because the page number format string contains characters special to the shell (specifically, an embedded blank), it must be enclosed in quotes. The alias command also requires that the entire alias definition be enclosed in quotes. Therefore, I need a quote within a quote.

If you understood the discussion of quotes in Chapter 9, you also should realize that there are at least three ways to write the pg alias command:

$ alias pg=`/usr/bin/ls -cns -p"Page %d:"`
$ alias pg="/usr/bin/ls -cns -p'Page %d'"
$ alias pg="/usr/bin/ls -cns -p\"Page %d\""

The first form is the form I chose for the example. The second form embeds a single quoted string inside a double quoted string; it works just as well. The third form uses an escape character to embed a double quote inside a double quoted string. In this case, the shell strips off the backslashes before it stores the alias value. I avoid this form because I don't like to use escape sequences unless I have to. An escape sequence is a two-character sequence, the first character of which is a backslash (\). The second character is the one that is being escaped, which means that the shell will not try and interpret the character in any way and just treat it as it is.

The point here is that alias definitions usually must be enclosed in quotes unless the alias value is a single word. Thus, you must occasionally embed quoted strings inside a quoted string. You should recognize that this need can arise. Be prepared to handle it by making sure that you understand how the shell-quoting mechanism works.


CAUTION: If you do get a handle on how the shell-quoting syntax works, it incites many otherwise nice people to brand you as a UNIX guru. So be careful.

Using Exported Aliases

The alias command supports a number of options, including -x (export) and -t (tracking).

An exported alias is much the same concept as an exported variable. Its value is passed into shell scripts that you invoke.

Exporting a command alias can be both helpful and harmful. Exporting the pg alias shown earlier would be helpful, for example, because it would cause pg commands issued by a shell script--many UNIX commands are implemented as shell scripts--to work as I prefer. On the other hand, if you define an alias for the rm command that always prompts the user before deleting a file, you might be inundated with requests from system-supplied shell scripts to delete temporary files that you've never heard of.

Use the command alias -x to display only those command aliases that are exported. When used in the form alias -x name, the alias name is redefined as an exported alias; it should have been defined previously. To define a new exported alias, use the full form

alias -x name=value

Using Tracked Aliases

By default, the Korn shell automatically creates a tracked alias entry for many of the commands you invoke from the keyboard. This feature helps to improve performance. When an alias is tracked, the Korn shell remembers the directory where the command is found. Therefore, subsequent invocations don't have to search your PATH list for the command file. Essentially, the alias for the command simply is set to the full pathname of the command.

You can display the commands for which a tracked alias exists by using the command alias -t.

To request explicit tracking for a command you use frequently, use the form

alias -t name

If no alias exists with the given name, the Korn shell performs a path search and stores the full pathname of the command name as the alias value. Otherwise, the shell simply marks the alias as tracked for future reference.

Note that you generally don't set the tracked attribute for command aliases that you write--that is, when the alias name differs from the alias value. The values for tracked aliases usually should be set by the Korn shell. You can achieve the effect of a tracked alias by supplying the full pathname of the command in the alias value; this eliminates path searches. The lx alias shown earlier, for example, would be better written as

alias lx=`/usr/bin/ls -FC'

This would achieve the same effect as tracking.

As a final example, suppose that the vi command is not in the list when you issue the command alias -t, but that you know you will be using the command fairly frequently. To request tracking for the vi command, simply issue the command alias -t vi.

One of the major reasons for name tracking is that the Korn shell takes account of the possibility that your search path--the value of the PATH shell variable--may include the directory . (dot), which is a reference to your current directory. If you switch to another directory, commands that were available might become unavailable, or they might need to be accessed by a different pathname. Alias tracking interacts with the cd command to keep the full pathnames of tracked aliases current. In other words, alias tracking keeps track of the proper full pathname for commands as you switch from directory to directory and create, remove, or relocate executable files. You can use the set command to toggle alias tracking on and off. Typing

set -o trackall

forces the shell to track every command you use, whereas

set +o trackall

switches the tracking off.

Shell Options

Because the Korn shell is a rather sophisticated program, it deals with many human-interface issues that might be resolved in two or more ways. To help you use the shell in ways most convenient to you, the shell enables you to choose how it behaves by setting options.

You can set Korn shell options in two ways: with the ksh command when you invoke the shell and with the set command from within the shell after you've started it. Options that you don't set explicitly take on a default value. Thus, you never need to bother with option settings unless you want to.

The ksh command generally is issued on your behalf by the UNIX logon processor, using a template stored in the /etc/passwd file for your logon name. Generally, the system administrator constructs the password entry for you, but unless he's very busy or very mean-spirited, he'll be happy to adjust your password entry to invoke the shell with your preferred settings. Of course, you can replace your logon shell with the Korn shell at any time by using this command:

$ exec ksh options ...

The exec statement you encountered in your study of the Bourne shell does the same thing under the Korn shell. It replaces the current shell with the command named as its first argument--usually also a shell, but perhaps of a different type or with different options and arguments.

The syntax of the ksh command follow:

ksh [ +/-aefhkmnpstuvx- ] [-cirs] [+/-o option] ... [+/-A name] [arg ...]

The -c, -i, -r, and -s options can be specified only on the ksh command line. All the other options can be specified on the set command as well.

Table 11.3 lists the options specifiable only on the ksh command line.

Table 11.3. Options specifiable only on the ksh command line.

Option Specifies Description
-c Command The first (and only) arg is a command. The -c option prevents the shell from attempting to read commands from any other source. It merely executes the command given as arg and then exits. This option is not used often from the keyboard or from within shell scripts. It most often is used internally by programs written in the C language.
-i Interactive shell Forces the shell to behave as though its input and output are a terminal. Usually, you don't need to specify the -i option explicitly. Its main purpose is to prevent the abnormal termination of commands invoked by the shell from terminating the shell itself.
-r Restricted shell The Korn shell runs as a restricted shell and prevents the user from using the cd command, modifying the PATH variable, redirecting output, and invoking a command by its full pathname. This option generally is of interest only to the system administrator for setting up specialized user accounts. The Korn shell also starts off as a restricted shell if the first character of its name when invoked is an r. Copying (or linking) ksh to rksh, for example, and then running rksh gives the same result as ksh -r. The reason for using the rksh form is that ksh -r isn't always guaranteed to run a restricted shell if it is defined as a logon shell in the password database /etc/passwd.
-s Standard input The Korn shell doesn't activate the protections against abnormal termination given by option -i. The shell reads commands from standard input until the end-of-file character and then exits normally. This is a handy option, because it enables you to pipe a stream of commands to the shell for execution.

Table 11.4 lists additional options you can specify on the ksh command or the set command. You can specify options with a letter in the usual way (for example, -a) or by name (for example, -o allexport). An option that has been set explicitly or by default can be turned off with the + flag, as in +a or +o allexport.

Table 11.4. Other options you can specify with the ksh command or the set command.

Option Description
-a The equivalent option name is allexport. All variables are treated implicitly as exported variables. You don't need to invoke the typeset -x command or export alias to export the variable. A variable becomes eligible for export when it is defined, whether by the typeset statement or by an assignment statement. The typeset-x command and export alias are permitted, but they have no additional effect.
-e The equivalent option name is errexit. Any command returning a non-zero exit code causes immediate termination of the shell. When this option is set within a shell script, only the shell script is terminated.
-f The equivalent option name is noglob. Filename expansion is disabled. Wildcard expressions are treated literally and, with the -f option in force, have no special meaning or effect. You might use set -f and set +f to disable wildcard expansion for a short range of statements.
-h The equivalent option name is trackall. Every command issued is defined automatically as a tracked alias, just as though you executed alias -t xxx in front of each command. The -h option is set to on by default for non-interactive shells. Commands that specify a full pathname or that use names not valid as command alias names are not tracked.
-k The equivalent option name is keyword. When -k is set, command arguments with the form name=value are stripped from the command line and are executed as assignment statements before the command is executed. The assignment is exported temporarily for the duration of the one command. The effect is equivalent to adding keyword arguments to the shell language and to UNIX commands and shell scripts that support this kind of argument. Most UNIX commands and shell scripts, however, do not support keyword arguments. Therefore, the -k option has little real application.
-m The equivalent option name is monitor. -m runs commands that you launch in the background--using the & shell operator--in a separate process group, automatically reports the termination of such background jobs, and enables use of the jobs command for managing background jobs. If -m is not set, commands launched with the & operator execute in the same manner as with the Bourne shell, and job control is not in effect. The default is to enable this option automatically for interactive shells.
-n The equivalent option name is noexec. -n causes the shell to read and process commands but not execute them. You can use this option in the form ksh -n shell-script-filename to check the syntax of a shell script. You probably won't want to use this option with your logon shell.
-p The equivalent option name is privileged. The -p option is useful for script writers. A shell script file that has the Set User ID bit, the Set Group ID bit, or both will, when invoked by the Korn shell, have the effective User ID and effective Group ID set according to the file permissions, the Owner ID, and the Group ID; also, the -p option will be on. In this mode, the shell script enjoys the permissions of the effective User ID and Group ID--not those of the real user. Setting the -p option off--for example, with set +p--causes the Korn shell to set the effective User ID and Group ID to those of the real user, effectively switching to the user's rather than the file's permissions. You subsequently can use the set -p command to revert to Privileged mode. Not all versions of the Korn shell support this definition of the -p option; only the more recent UNIX operating system releases include this facility.
-s When used on the set command, -s sorts the arg command arguments into alphabetical sequence before storing. When used with the ksh command, the -s option reads commands from the standard input (see Table 11.3).
-t The Korn shell, when invoked with the -t option, reads and executes one command and then exits. You should set the -t option with the ksh command instead of with the set command.
-u The equivalent option name is nounset. -u causes the shell to generate an error message for a reference to an unset variable--for example, referring to $house when no value has been assigned to house. The default behavior is to replace the variable reference with the null string. This option is useful to script writers for debugging shell scripts.
-v The equivalent option name is verbose. Each command is printed before scanning, substitution, and execution occur. This is useful for testing shell scripts when used in the form ksh -v shell-script-filename or with set -v and set +v from within a shell script to force the display of a range of commands as they are being executed.
-x The equivalent option name is xtrace. -x causes the Korn shell to display each command after scanning and substitution but before execution. Each line is prefixed with the expanded value of the PS4 variable. Using this option enables you to see the effects of variable and command substitution on the command line. When used in the form ksh -x shell-script-filename the -x option is a handy debugging tool for script writers.
-- Used with the ksh or set command, this option forces interpretation of the remaining words of the command line as arguments rather than options--even for words beginning with - or +. The -- option often is used with the set command for setting new values for the positional parameters, because it ensures that no substituted values are construed as set statement options.


CAUTION: Use caution when writing scripts that will use the privileged option. A badly written script may give potential attackers doors they need to access a more privileged user.

In addition to the letter options listed in Table 11.4, the -o keyletter supports the additional named options listed in Table 11.5.

Table 11.5. Options supported by the -o keyletter.

Option Description
bgnice Requests that the shell automatically reduce the priority of background jobs initiated with the & shell operator as though the nice command had been used.
emacs Invokes the EMACS Edit mode. EMACS editing remains switched on until set +o emacs or set -o vi is entered.
gmacs Invokes the GMACS (Gosling EMACS) Edit mode with the alternative definition of the Ctrl+T transpose function.
ignoreeof Requests that the shell ignore an end-of-file character entered at the beginning of the command line. Ordinarily, an EOF character entered in this position causes the shell to terminate. You can set this option to avoid accidentally terminating the shell. You must use the exit command to terminate the shell and log out.
markdirs Causes wildcard expansion to append a slash (/) to any generated pathnames that are the pathnames of directories.
noclobber Modifies the behavior of the > redirection operator to inhibit the overwriting of existing files. If you name an existing file after >, the shell writes an error message and doesn't open the output file. Use >| to redirect output to an existing file when noclobber is set.
nolog Inhibits the storing of functions in your command-history file.
vi Enables the vi Edit mode with line input. Line input provides only a subset of the features of vi command editing, but it provides better performance than the viraw option. You can switch off vi Edit mode with set +o vi or set -o emacs.
viraw Enables vi Edit mode with character input. Character input provides all the features of vi Edit mode but with more overhead than the vi option.

The -A option can be used on the ksh command line or the set command to define an array variable with initial values. When you specify -A, the next argument must be the name of the array variable to be initialized. Subsequent arguments are stored as consecutive elements of the array, beginning with element 0. The -A option resets any previous value of the array variable before it assigns new values. Thus, the ending value of the array consists of only those arguments specified as arg.

The +A option assigns the arg values successively, starting with element 0, but it doesn't reset any previous values of the array. Thus, if the array variable previously had 12 values and only six values were provided with +A, after execution, the first six elements of the array would be the arg values and the last six elements would be left over from the previous value of the array.

The significance of the arg values depends on the options specified. If option -A is specified, the values are taken as initial array element values. If option -s or -i is specified, or if option -i defaults because the shell input is a terminal, the arg values are used to initialize the positional parameters $1, $2, and so on. If option -c is specified, the first arg is taken as a command string to be executed. If none of the options -A, -c, -i, or -s is specified, the first arg is taken as the name of a file of shell commands to be executed, and subsequent arg values are set temporarily as the positional parameters $1, $2, and so on during the file's execution.

Command History

Command history and command editing are somewhat interrelated features. To fully use all the benefits of command editing, however, you need an understanding of how command history works.

Command history is simply the automatic recording of commands that you enter in a numbered list. The list is kept in a special disk file in your home directory to preserve it from logon session to session. Therefore, when you log on, the command-history list from your previous session is available for reference and use. New commands you enter are added to the end of the list. To keep the list from growing too large, the oldest commands at the beginning of the list are deleted when the list grows to a certain fixed size.

You don't need to do anything to activate the command-history feature, and you don't need to specify its maximum size. Its operation is completely automatic. Your only mission, should you decide to accept it, is to use the list to make your life easier.

You can use the command-history list in one of three ways:


NOTE: Performing command editing with the fc command, although a convenient and useful feature of command history, is not the same as the command-editing feature discussed later in the "Command Editing" section.

Now take a closer look at these commands for viewing and manipulating command history.

Displaying the Command-History List

The command history command displays the commands in the command-history list. Each command is listed with a line number preceding it. The line number uniquely identifies each command in the history list, and it is one way you can refer to a specific line in the history list--for example,

$ history
[122] cd /usr/home/jim/src/payapp/pay001
[123] vi main.c
[124] cc -I../include -o main main.c
[125] fgrep include *.c | grep `^#'
[126] vi checkwrite.c checkfile.c checkedit.c
[127] lint -I../include checkfile.c >errs; vi errs
[128] vi checkfile.c
[129] cc -I../include -o checks check*.c
[130] cp checks /usr/home/jim/bin


NOTE: The history command is actually an alias for the fc command--specifically, for fc -l.

The complete syntax for the history command follows:

history [first] [last]

For first, specify the first line to be displayed. You can designate a specific line directly by its line number--for example, history 35--or as a number of lines back from the current line--for example, history -10. You also can give the command name of the line from which the display should begin--for example, history vi. The Korn shell looks backward from the current line until it finds a command beginning with vi and then displays lines from that point forward.

For last, specify the last line to be displayed. If you omit last, history lines are displayed from first up to the current, most recently entered line in the command history. You can use an actual line number, a relative line number, or a command name to designate the last line to be displayed.

If you omit both first and last, the Korn shell lists the last 16 lines of history.


TIP: You won't know what line numbers to use until you first list some history. Most people begin a search of command history without any operands. If you want to see more lines before line number 160, you might want to try history 140.

Reexecuting a Command from the History

The r command enables you to reexecute a command from the command-history list. The r command itself isn't added to the history, but the command you reuse is added.


NOTE: The r command is actually a preset alias for the fc command--specifically, fc -e -.

The general syntax for r follows:

r [ old=new ] [ line ]

If you omit line, the most recently entered command is reexecuted.

Specify a line number (25), a relative line number (-8), or a command name (vi) for line to designate the command you want to reuse. As with the history command, if you specify a command name, the most recently entered command with that name is reused.

You can modify a word or phrase of the reused command by using the syntax old=new. Suppose that the command history contains the following line:

135 find /usr -type f -name payroll -print

You could reuse the find command, changing only the filename payroll to vendors, like this:

$ r payroll=vendors find

The r command echoes the line that will be executed, showing any changes that might have been made. For example, the r command here yields the following output:

$ r payroll=vendors find
find /usr -type f -name vendors -print

Accessing the History List: fc

The fc (fix command) command is a built-in Korn shell command. It provides access to the command-history list. Forms of the fc command enable you to display, edit, and reuse commands you previously entered. The Korn shell automatically defines the alias names history and r for you to reduce the amount of typing needed to perform simple history functions.

The syntax of the fc command follows:

fc [ -e editor ] [ -nlr ] [ first ] [ last ]

When invoked with no options, the fc command selects a line from the command history using the values of first and last, invokes the default command editor, and waits for you to edit the command or commands selected. When you exit the editor, by filing the altered command text or by quitting the editor, the commands are executed.

The fc command actually copies the selected commands to a temporary file and passes the file to the text editor. The contents of the file after editing become the command or commands to be executed.

For example, if you enter the command

$ fc vi

where vi represents the value of first, the Korn shell copies the most recent vi command to a temporary file. The temporary file has an unrecognizable name, such as /usr/tmp/fc13159, and is located in a directory designated for temporary files. The file you actually edit is /usr/tmp/fc13159. Regardless of whether you change the text in file /msr/tmp/fc13159, the Korn shell executes its contents immediately after you exit the editor.

You can specify the command or commands to be processed in the following manner:

135 mkdir paywork
136 mv paymast/newemps paywork
137 cd paywork
138 vi newemps
139 payedit newemps

The first and last command-line selectors don't have to use the same formats. You could select line 145 of the history list through the fifth-to-last line by entering fc 145 -5, for example.

By default, the fc command invokes a text editor on the selected lines and reexecutes them after editing. You can modify this default behavior with the options shown in Table 11.6.

Table 11.6. Options to modify the behavior of the fc command.

Option Stands For Description
-e Editor Use the -e option to override the Korn shell's default editor. To use the vi editor to modify and reuse commands, for example, type fc -e vi .... Use fc -e vi ... to override the default editor.

The special format -e - suppresses the use of an editor. The selected lines are executed immediately with no opportunity to change them. This form of the fc command--as in fc -e - 135--is equivalent to the r command. When you use this form, the second dash must be a word by itself. The command fc -e - 135 immediately reexecutes line 135 of the command history, whereas the command fc -e -135 attempts to edit the most recent command in the history list with an editor named -135, which probably doesn't exist. Alternatively, the command fc -e- 135 generates another kind of error, because -e- isn't a valid option of the fc command.
-l List The selected lines are listed. No editor is invoked, and the lines are not reexecuted. The command fc -l is equivalent to the alias history.
-n Numbers Use the -n option to suppress the printing of line numbers in front of the command history. The -n option is meaningful only in combination with the -l option--for example, fc -nl.
-r Reverse The -r option causes the command history to be printed in reverse order. The most recently entered command is shown first, and successive lines show progressively older commands. Use the -r option with the -l option--for example, fc -lr.

Command Editing

Command editing is arguably the most important extension of the Bourne shell included in the Korn shell. It is a great time-saver, and it makes the shell much easier to use for UNIX beginners.

The basic idea of command editing is to enable you to use common keys on most terminal keyboards to correct keying errors as you enter commands.

To bring this basic idea to reality, the Korn shell must have some support from the terminal you're using. If you're going to backspace and retype a character, for example, it would be helpful if the terminal is capable of backspacing, erasing a character already displayed, and typing a new character in its place. For this reason, command editing is most useful with video-display terminals. Hard-copy terminals such as teletypes are inappropriate for use with the command-editing feature of the Korn shell.

The Korn shell supports two distinct styles of command editing: vi Edit mode--named after the vi text editor--and EMACS Edit mode--named after EMACS. If you're familiar with either of these editors, you can begin to use command editing immediately.

Activating Command-Edit Mode

Before you can use command editing, you first must activate it. Until you do so, the Korn shell command line works much the same as the Bourne shell: Everything you type goes into the command line indiscriminately as text, including control and function keys. This is a compatibility feature you'll want to disable as soon as possible--typically, by activating command editing in your logon profile.

To enable vi Edit mode, enter the following command line or place it in your profile (see "Customizing the Korn Shell," later in this chapter):

set -o vi

To enable EMACS Edit mode, enter the following command line or place it in your profile:

set -o emacs

If you're not familiar with the vi or EMACS text editors, but you want to use command editing, read through the following sections and choose the editing interface you find most natural.

vi Edit Mode

vi Edit mode uses the editing commands and methods of the vi text editor, although with some minor differences due to the fact that you're editing only one line of text and not an entire file.

You can activate vi Edit mode by entering this command:

set -o vi

If you prefer to always use the vi Edit mode, add the command to your profile. Note that you can't have the vi and EMACS Edit modes both active at once, though. You can switch between them or shut them both off.

Just like the vi editor, vi command-editing uses two modes: Command and Input. Normally, your keyboard is in Input mode, and every character you type is entered into the command line. To enter Command mode, press ESC. In Command mode, the upper- and lowercase letters of the keyboard represent editing commands, and pressing a key causes an editing action. If no command corresponds to a given key, pressing it in Command mode causes the terminal to beep; you cannot enter text in Command mode. This error is the most common mistake beginners make with vi-style editing. It is a stumbling block responsible for the vi editor's miserable reputation as a text editor.

Pressing the Enter key always returns you to Input mode. After you make any editing changes to the line, you can press Enter no matter where your cursor is in the line to enter and execute the command.


CAUTION: Keystrokes you type while in Command mode are not displayed. You can see only the effect of an edit command--not the command itself. This can be unsettling if you're inexperienced with the vi style of editing or if you're entering a command of more than a few keystrokes.


TIP: If you forget whether you're in Command or Edit mode, the invisible nature of Command mode can make your keyboard appear to go wild and not respond to your input in any recognizable fashion. If this happens to you, the best thing to do is to try to cancel the current line completely with the kill function--normally, by pressing the @ or Ctrl+U keys. If all else fails, press the Enter key. Pressing the Enter key might give you an error message when it attempts to execute a garbled command, but at least it is guaranteed to return you to Input mode.

Table 11.7 summarizes the vi Edit mode commands. As you'll notice if you're already familiar with vi, nearly all the vi commands are supported--even those that cause movement upward and downward in a file. Commands that move from one line to another actually cause movement in the history file. This enables you to browse through the command history, select a command, modify it if necessary, and reenter it--all with a few simple keystrokes.

Some commands can be prefixed by a count--a non-zero number. A count causes the command to be repeated that number of times. For example, B moves backward one word, but 12B moves backward 12 words. If you don't specify a count, it defaults to 1.

A few commands--notably c (change), d (delete), and y (yank)--must be followed by a cursor-motion command. Such commands are marked with the right-arrow symbol ([ra]). Using cursor-motion commands is discussed after Table 11.7.

Table 11.7. vi command editing: Command-mode commands.

Command Action
a Inserts text after the cursor.
A Inserts text at the end of the line.
[n]b Moves backward one word.
[n]B Moves backward one blank-delimited word.
[n]c[ra] Changes text.
C Changes to end of line.
[n]d[ra] Deletes text
dd Discards the entire current line.
[n]D Deletes to end of line.
[n]e Moves to end of current word.
[n]E Moves to end of blank-delimited word.
[n]fc Moves cursor to next c in current line.
[n]Fc Moves cursor to previous c in current line.
[n]G Moves to the last--least recent--line in the command history. If nG is entered, it selects line n from the command history.
[n]h Moves the cursor one position to the left.
i Inserts text before cursor.
I Inserts text in front of the first nonblank character of the line.
[n]j Moves down one line--that is, to a more recent history line. This command discards whatever you have typed on the current line.
[n]k Moves up one line--that is, to a less recent history line. This command discards whatever you have typed on the current line.
[n]l Moves cursor one position to the right.
n Repeats the previous / or ? command.
N Repeats the previous / or ? command but in the reverse direction. It causes a / command to be repeated as the equivalent ?, and ? to be repeated as the equivalent of /.
[n]p Inserts text into the edit buffer after the current cursor position.
[n]P Inserts text into the edit buffer before the current cursor position.
[n]rc Replaces the current character with c. A repeat factor replaces n consecutive characters with c.
R Replaces characters in the current line--Replace mode. This command differs from C in that it does not discard characters following the cursor; only as many characters as you type are replaced. You end Replace mode by pressing Enter or ESC.
S Deletes entire line and enters Input mode.
tc Moves cursor to the next c in the line.
Tc Moves cursor to the previous c in the line.
u Undoes the last text change. You can undo the previous u command. Successive u commands alternate between the original and the changed form of text.
U Undoes all changes to the current line.
[n]v Edits the current command--or line n of the history file--with the vi editor. When you exit vi, the edit file is executed as commands, one per line.
[n]w Moves cursor to next word.
[n]W Moves cursor to next blank-delimited word.
[n]x Deletes characters after the cursor.
[n]X Deletes characters before the cursor.
[n]y[ra] Yanks text into the edit buffer.
yy Yanks (copies) the entire current line.
Y Yanks (copies) text to the end of line.
^ Moves cursor to the first character of the line that is not a space or tab.
0 Moves cursor to the first position of the line.
$ Moves cursor to the last character of the line.
[n]- Moves to the preceding line in the command history.
[n]+ $ Moves to the next line in the command history. Use + only if you have used - or k to move backward in the history file. Use G to skip back to the earliest line in the history file.
[n]| $ Moves to the nth character of the line--that is, to column n.
[n]_ $ (underscore) Inserts the last (nth) word of the previous command.
/string$ Selects the most recent line in command history that contains string. string cannot be a regular expression.
/^string$ Same as / except that it selects only a line that begins with string. That is, / selects a line that contains string anywhere in the line, but /^ looks only for lines that begin with string in column 1.
?string$ Searches forward in the history file--that is, toward more recent lines--until it finds a line that contains string. The selected line replaces the current line. string cannot be a regular expression.
?^string Same as ? except that it selects only a line that begins with string. That is, ? selects a line that contains string anywhere in the line, but ?^ looks only for lines that begin with string in column 1.
; $ Repeats the previous f, F, t, or T command.
, Repeats the previous f, F, t, or T command but reverses the search through the command.
~ Inverts the capitalization of the current character.
. Repeats the previous text-modifying command.
# Inserts a pound sign (#) at the beginning of the line. If you then press Enter, the shell treats the line as a comment, and the line is added to the command history.
= Lists filenames in the current directory that begin with the same characters as the current word. The listed filenames are not inserted into the current line, and the current line is not changed. You can use the displayed information to select a file, though, and finish typing a complete filename.
\ Appends characters to the word containing the cursor so that the word forms a valid pathname. The shell searches the current directory--or the directory specified by the incomplete word--for filenames that begin with the same characters as the word. Then it appends characters from the matching filenames until a full filename is formed, or, in the case of multiple matches, the filenames differ. This command is a handy way to abbreviate a filename or to enter a filename when you can remember only a few leading characters of the name.
* Replaces the word with the list of filenames in the current directory--or in the directory specified by the word--that all begin with the same characters as the replaced word. This has the same effect as the wildcard expression string* if entered directly, except that the filenames are entered into the command line now instead of during shell processing.
Space Moves cursor to the right. It doesn't change characters spaced over.
Backspace Moves cursor to the left. It doesn't change characters backspaced over.
Enter Executes the current command line.
Ctrl+L Redraws the current line. This command is useful if the screen becomes garbled. It redraws only the display line used for command input--not the entire screen.


NOTE: Although many vi editors support cursor keys for cursor control, the vi Edit mode does not recognize these keys, so you must use h, j, k, and l to control the cursor.

The vi command-editing feature also supports a few control operations you can use while in Input mode, which are described in Table 11.8. Using one of these operations doesn't require you to switch to Command mode first, and it doesn't switch you to Command mode.

Table 11.8. vi Command editing: Input mode commands.

Control Action
Enter Executes the command line. You can press Enter while in Command mode or Input mode, regardless of the current cursor position. If the cursor is somewhere in the middle of the line, pressing Enter doesn't truncate the remainder of the line; instead, it executes the whole line.
Erase Normally the # or Backspace key. This is the erase function defined with the stty command. The cursor is backspaced, and the character at that position is erased.
Kill Normally the @ or ^u (Ctrl+U) character. This is the kill function defined with the stty command. The current line is discarded; the input line is erased and the cursor returns to the start of the line. Notice that this differs from the normal shell action when command editing is not in effect. Normally, the kill function scrolls the discarded line up and starts a new line below it.
Ctrl-v Escapes the next character. It enables you to enter the Erase, Kill, or \ character as data, avoiding the normal control function.
Ctrl-w Deletes the previous word. It is similar to Backspace, but it backspaces over the preceding word instead of the preceding character.
\ Escapes the next Erase or Kill character. It is similar to Ctrl+V, but it doesn't escape other commands.

Most vi commands can be preceded with a repeat factor, shown in the box as [n]. If you omit the repeat factor, the command executes its normal function one time. A repeat factor larger than 1 causes the command to repeat its action the specified number of times. Thus, 2W causes the cursor to skip forward not one but two words, and 7r. replaces seven characters, starting at the cursor position, with seven periods.

Commands shown with the symbol [ra] require a cursor motion command following the main command letter. The c, d and y commands must be followed by a cursor motion command to define the amount of text to be changed, deleted or yanked (copied). The cursor motion command can be any command that, if by itself, would move the cursor beyond the desired text. For example, dw deletes the current word. cte changes text up to, but not including, the next e in the line. y0 yanks the characters from the beginning of the line up to, but not including, the character at the cursor position.

Framing cursor-motion commands to meet your text-editing objectives is your responsibility. No prespecified limitations exist on the method for selecting a range of text; you are free to choose whatever comes naturally to you. Until you are comfortable with the use of cursor-motion commands, however, stick to simple combinations, such as cw or cW, to change a word.

The capitalized cursor-movement commands B, E, and W differ from their lowercase counterparts in their choice of delimiters. The lowercase b, e, and w commands consider a word to end at the next nonalphanumeric punctuation character, which can be a blank or tab but also includes apostrophes, commas, and so on. The B, E, and W commands consider a word to be delimited strictly by blanks or tabs. They skip over, or select, punctuation characters as well as alphanumerics.

Most of the commands leave you in Command mode. A few--a, A, c, C, i, I, R, and S--switch to Input mode to enable you to enter text. If, after entering the text, you are ready to execute the command, simply press Enter. If you want to edit the line some more, however, you must switch back to Command mode. In that case, press ESC after entering the desired text.

Not all commands supported by the vi editor are shown in Table 11.8. Commands not shown are not supported by the built-in vi Edit mode of the Korn shell. Noteworthy omissions include the o and O (open) commands, the m (mark) command, and scrolling commands such as z, H, and M. These omissions are due to the difference between a command editor and a file editor. In a command-editing context, they have no useful purpose.


NOTE: For a fuller discussion of the vi text-editing commands, refer to Chapter 3 in Volume II, "Text Editing with vi, and emacs."

EMACS Edit Mode

The EMACS Edit mode is designed to parallel the editing interface offered by the EMACS editor. The EMACS editor is not so widely available as the vi editor, but many people feel that its modeless, full-screen editing style is more natural than vi. Be that as it may, a modal editing style is well suited to command editing. Even if you're already an EMACS devotee, you might want to try your hand at the vi Edit mode before discarding it out of hand.

The EMACS Edit mode is activated when you enter this command:

set -o emacs

If you prefer to always use the EMACS Edit mode, you can add the command to your .profile file. Note, however, that you can't have the EMACS and vi Edit modes both active at once. You can switch between them or shut off both of them.

Because the EMACS editing interface is modeless, you always can enter text into the current line. To perform an editing operation, you generally enter a command prefixed by the ESC key. Therefore, commands generally require at least two keystrokes. Because ESC isn't conveniently located on most keyboards, entering a series of editing commands is quite a feat of gymnastics.

The EMACS keyboard commands are described in Table 11.9. Numbered Notes specific to the commands discussed in the Table immediately follow Table 11.9. The commands are listed in alphabetical order by the command letter, with special characters (*, =, and so on) listed first. All commands are one letter, preceded by Ctrl or ESC. As usual, you hold down the Ctrl key while pressing the command letter, but you press and release ESC before pressing the command-letter key. Several notes explaining the table entries are located after this table.

Many commands enable you to specify a repeat count in the form Esc n before the command. The repeat count repeats the action of the command that number of times or specifies a column relative to which the command should operate. The value of n starts at 1. Esc 1 executes the command once; it is the same as omitting Esc n, or column 1 of the current line.


CAUTION: The EMACS Edit mode edits lines--not commands. Command history might contain multiline commands, such as if or while, if you use such commands at the keyboard. The vi Edit mode processes such commands as a single entity, but in EMACS Edit mode, you might need to use the Ctrl+O (operate) command to step through multiline commands when you retrieve them from the command history.

The EMACS command-editing interface is an example of a user interface designed for an alien species, because it obviously requires the use of three hands to perform well. If you are a beginner or a casual user of command editing, you might nevertheless find EMACS Edit mode preferable to vi mode, because, with EMACS, there's no confusion between Command mode versus Input mode. As your proficiency and keyboard speed increase, however, the vi Edit mode becomes a more attractive interface.

Table 11.9. EMACS Edit mode commands.

ESC n Key Sequence Action
Enter Executes the current line. On some terminals, it is labeled Return.
Erase The stty erase character. It deletes the character preceding the cursor.
ESC n Erase Backspaces n characters.
Kill Deletes the entire line. When entered twice in quick succession, it causes subsequent Kill characters to print blank lines.
\ Escapes the next character, enabling the Erase, Kill, EOF, and ESC characters and Ctrl-x characters to be entered into the current line. The \ itself is discarded. Type \\ to enter a single backslash.
ESC ESC Appends characters to the current word to complete the pathname.
ESC Space Sets a mark at the cursor position.
ESC * Performs a pathname expansion on the current word as though an * were appended and replaces the word with the list of pathnames that match, if any.
ESC = Lists pathnames that match the current word, as though * were appended to the word. The current line is not changed.
ESC < Fetches the least recent line from command history.
ESC > Fetches the most recent line from command history.
ESC . Inserts the last word of your preceding command at the current cursor position.
ESC n ESC . Inserts the nth word of your previous command at the cursor position.
ESC _ Same as ESC ..
ESC Ctrl+? Same as ESC Ctrl+H. (see Note 1)
ESC n ESC Ctrl+? Same as ESC Ctrl+H. (see Note 1)
ESC letter Invokes the macro defined as an alias named _letter. (see Note 2)
Ctrl+] c Moves cursor to next occurrence of character c in this line.
Ctrl+A Moves cursor to start of line.
Ctrl+B Moves cursor left one character. (see Note 3)
ESC n Ctrl+B Moves cursor left n characters.
ESC b Moves cursor to beginning of word.
ESC n ESC b Moves back n-1 words.
Ctrl+C Makes the current character uppercase.
ESC n Ctrl+C Makes n characters uppercase.
ESC c Makes everything to end of current word uppercase. (see Note 4)
ESC n ESC c Uppercases n words from cursor position. (see Note 4)
Ctrl+D Deletes one character. (see Note 5)
ESC n Ctrl+D Deletes n characters. (see Note 5)
ESC d Deletes to the end of the current word.
ESC n ESC d Deletes to end of nth word right.
Ctrl+E Moves cursor to end of line.
Ctrl+F Moves cursor right one character. (see Note 3)
ESC n Ctrl+F Moves cursor right n characters.
ESC f Moves cursor right one word.
ESC n ESC f Moves cursor right n words.
ESC h Same as ESC Ctrl+H.
ESC n ESC h Same as ESC n ESC Ctrl+H.
ESC Ctrl+H Deletes backward to beginning of current word. (see Note 6)
ESC n ESC Ctrl+H Deletes backward to beginning of nth previous word. (see Note 6)
Ctrl+J Same as Enter.
Ctrl+K Deletes to end of line.
ESC n Ctrl+K Deletes characters back to or up to column n.
Ctrl+L Redisplays the entire current line.
ESC l Makes all characters to end of current word lowercase. (see Note 4)
ESC n ESC l Makes n words from cursor position lowercase. (see Note 4)
Ctrl+M Same as Enter.
Ctrl+N Fetches the next line from the command-history file. Successive presses retrieve more recent lines in progression. (see Note 3)
ESC n Ctrl+N Fetches the nth line forward from your present position in the command-history file.
Ctrl+O Executes the current line and then fetches the next line from the command history. (see Note 7)
Ctrl+P Replaces the current line with the last line of the command history. Successive presses retrieve consecutively older lines from the command history. (see Note 3)
ESC n Ctrl+P Fetches the nth line back from the command history.
ESC p Copies text from cursor to the mark into an internal buffer. To set a mark use ESC Space. This will mark the current position internally as a reference point.
Ctrl+R string Enter Searches command history for the most recent line containing string. To repeat the preceding search, omit string.
ESC 0 Ctrl+R string Enter Searches the command history starting at the oldest line forward for the first occurrence of string. To repeat the preceding search, omit string.
Ctrl+R ^string Enter Same as Ctrl-r, except that it matches string only at the beginning of a line.
ESC 0 Ctrl+R ^string Enter Same as ESC 0 Ctrl-r, except that it matches string only at the beginning of a line.
Ctrl+T Transposes the current and next characters. (see Note 8)
Ctrl+U Multiplies count of next command by 4. Thus, Ctrl-u Ctrl-f moves the cursor right four positions.
Ctrl+V Displays the current version of the Korn shell. To redisplay the current line, press any key.
Ctrl+W Deletes characters from cursor to mark. Marks are set using ESC Space.
Ctrl+X Ctrl+X Moves cursor to the mark position, setting a new mark at the old cursor position. This is called swap cursor and mark.
Ctrl+Y Inserts most recently deleted text at the current cursor position.


NOTE: The sequence Ctrl+? is not to be taken literally. It represents the ASCII Del (127) character. Most terminals generate the Del character in response to the Delete key, in which case ESC Delete is a synonym for ESC Backspace.


NOTE: A macro is defined with the alias shell built-in command. Its name must begin with an underscore (_) and must be followed by one letter. The value of the alias is processed as if you typed the characters of the value at the time of invoking the macro. Thus, sequences such as Ctrl-f in the alias value move the cursor to its current position. The letter used in the macro name should not be b, c, d, f, h, l, or p; these letters already are assigned to EMACS commands.


NOTE: In addition to using the control-key sequences to move the cursor, you can use the cursor-control keys to navigate the history list and move the cursor. The capability to do this depends on how your terminal is set up, though.


NOTE: Changing character case also moves the cursor to the right, spacing over the changed character(s).


NOTE: If the Ctrl-d key is assigned to the EOF function with the stty command, it is interpreted as your EOF key when typed at the beginning of the line. Otherwise, it performs the Delete function.


NOTE: Most terminals generate Ctrl-h for the Backspace key. Some terminals generate the ASCII Del character (0177), though. Therefore, the shorthand ESC Backspace might not work for your terminal.


NOTE: To use the operate (Ctrl-o) command, you must have previously established a position in the command-history file by using Ctrl-p, Ctrl-n, or another history command. Successive presses of Ctrl-o step through lines of command history in the forward--older to newer--direction, executing one line at a time. You have the opportunity to change each line before pressing Ctrl-o to execute it.


NOTE: If set -o gmacs is used instead of set -o emacs, Ctrl-t transposes the current and preceding character, not the current and next. This is the only difference between EMACS and GMACS Edit modes.

Variables

You were introduced to the concept of shell variables in Chapter 9. Everything you learned there remains true for the Korn shell. The Korn shell provides some significant extensions to shell variable support, though. Among these is a greatly expanded set of variables that have special meanings to the shell. These variables often are called predefined variables, because the shell provides an initial default value for them when you log on. The Korn shell also supports array variables and enhanced arithmetic on shell variables, both of which are a great boon to shell-script writers. Naturally, the syntax of shell variable references is expanded to support these capabilities.

Predefined Variables

Variables that have special meaning to the shell fall into two main groups: those you can set to affect the behavior of the shell, and those the shell sets for you to provide information.

Variables whose values are set by the shell include the familiar $@, $*, $#, $-, $?, and $$, as well as the new $!. The new variable $! provides the Process ID of the last command you invoked. It differs from $$ in that the value of $$--your current Process ID--generally is that of the shell itself and doesn't change, whereas the value of $! changes each time you invoke a command. The values of the other shell variables have the same meanings as they do in the Bourne shell.

Table 11.10 lists the named variables set by the Korn shell.

Table 11.10. Named variables set by the Korn shell.

Variable Description
_ Starts of as the full pathname of the last command you invoked. It then becomes the last argument of the preceding command, though, so if you type after the command ls -l, the value of $_ is -l. This variable also is used by the shell to hold the name of the MAIL file when checking for mail. This variable is not really of any use and is used internally by the shell.
ERRNO The nonzero exit code of the last command that failed. This variable is similar to $?, but it differs because its value changes only when a command fails. Successfully executed commands don't change the value of $ERRNO. This variable is primarily a diagnostic aid for use at the keyboard; it is of little use to shell scripts.
LINENO This variable is meaningful only within a shell script. Its value is the line number of the line in the script currently being executed. You can assign a value to LINENO, but it will be changed by the next shell script you invoke. Or, if it is inside a shell script, it will be changed by the next line executed.
OLDPWD The value of this variable is always the full pathname of the directory that was current immediately before the last cd command. In other words, repeated executions of cd $OLDPWD switch you back and forth between your current and preceding directories. An important use of the $OLDPWD variable is to facilitate cp and mv commands. cd someplace followed by cp filelist $OLDPWD copies files to your original directory without you having to type the full directory pathname. Then use cd $OLDPWD to switch back to your original directory. (In the Korn shell, the shorthand cd - means the same thing as cd $OLDPWD.)
OPTARG This value is set by the getopts command--a new built-in command provided by the Korn shell. (For more information, see "Shell Programming," later in this chapter.)
OPTIND This value is set by the getopts command. (For more information, see "Shell Programming," later in this chapter.)
PPID This value is your current parent Process ID. That is, if $$ is the current Process ID, $PPID is the Process ID of the parent process of $$. This variable is useful especially to shell script writers. It has little use at the keyboard.
PWD Specifies the full pathname of your current directory. Because of symbolic links, the value of $PWD isn't necessarily the same as the value printed by the pwd command. Suppose that a directory /usr/bin exists and that a symbolic link to /usr/bin exists named /bin. After cd /bin, the pwd command prints /usr/bin--the real pathname of the directory--but the statement print $PWD prints /bin--the pathname by which you reached the directory. (Links are explained in Chapter 4, "The UNIX File System.")
RANDOM This value is an integer in the range of 0 to 32,767. The value is different in a random way every time you examine it. This variable is not much use at the keyboard; however, in shell scripts, it is useful for generating temporary filenames.
REPLY The select statement, which is new with the Korn shell, sets the value of $REPLY to the user's input text. The read built-in command stores the user's typed input in $REPLY if you supply no variable names on the read command. (For more information, see "Using the select Statement," later in this chapter.)
SECONDS The integer number of seconds since you invoked the Korn shell--usually, since you logged on, unless you explicitly invoked the Korn shell with the ksh command. This variable simply records the wall-clock time the Korn shell has been running at your terminal.

The shell variables set by the Korn shell listed in Table 11.10 don't require your attention. If you have a use for one of them, refer to this table while at your keyboard or in a shell script. You don't need to assign values to them, though. In some cases, you aren't even allowed to assign a value.

Some variables require attention from you, however. In most cases, the Korn shell assigns a default value to these variables when it starts. You can override this default value in your logon profile--a file named .profile in your home directory--or at any later time by using an assignment statement from the keyboard. The values of these variables affect the way the Korn shell works. Proper setup of these variables can enhance your effectiveness and productivity.

Table 11.11 lists the variables used by the Korn shell.

Table 11.11. Variables used by the Korn shell.

Variable Description
CDPATH The value of $CDPATH is a list of colon-separated directory pathnames. The value is referenced only by the cd command. Use the CDPATH variable to name a list of directories to be searched when you issue cd with a directory's simple filename. The benefit of CDPATH is that it enables you to switch to a directory by giving only its filename instead of the full pathname. There is no default value for CDPATH.
COLUMNS The value of $COLUMNS defines the display width used by the Korn shell Command-Edit mode--either vi or EMACS--as a view window for long lines and as the screen width for printing the select list. The default value is 80.
EDITOR The value of $EDITOR is used primarily by programs other than the Korn shell. If you set the value of EDITOR (in your profile or at the keyboard), however, the Korn shell inspects the value for a pathname ending in vi or emacs. If either value is found, the Korn shell sets the corresponding vi, emacs or gmacs option, enabling command editing. This is only a convenience. You still can toggle the Command-Edit mode by using the set -o command. There is no default value for EDITOR.
ENV The value of $ENV is the pathname of a shell script containing commands to be executed when the Korn shell is invoked. Note that the Korn shell is implicitly invoked every time you invoke a command written as a Korn shell script. You also can invoke the Korn shell from within other UNIX commands such as vi and pg. By placing alias, export, and set commands in a file and supplying the file's pathname as the value of $ENV, you can ensure that you have the same shell environment whenever you invoke the Korn shell. Keep the file pointed to by $ENV small, because its execution is added to the execution of every shell script you execute. (For more information, see "Customizing the Korn Shell," later in this chapter.) There is no default value for ENV.
FCEDIT The value of $FCEDIT is the pathname of the text editor to be invoked by the fc command. You can override the value of FCEDIT by using the -e option with the fc command. The default value of FCEDIT is /bin/ed.
FPATH The value of $FPATH is a colon-separated list of directories--the same format as for CDPATH and PATH. The directory list is searched for autoload function definitions. (See "Shell Programming," later in this chapter, for a discussion of autoload functions.) There is no default value for FPATH.
HISTFILE HISTFILE is the filename of the Korn shell history file. If you want to specify an explicit filename for your history file, supply a value for HISTFILE in your logon profile. The default value of HISTFILE is $HOME/.sh_history.
HISTSIZE The value of HISTSIZE is an integer number specifying the maximum number of commands--not lines--to be retained in the history file. The shell may retain more than HISTSIZE commands in memory while you are working, but it will not accumulate more than HISTSIZE commands in the history file on disk. Note that a value you set for HISTSIZE is treated somewhat like a suggestion; depending on the specific version of the Korn shell you are using, it may act as a fixed upper limit to the number of commands remembered or as an at-least value. The default value of HISTSIZE is 128.
HOME HOME with the Korn shell works the same as it does with the Bourne shell. The value of HOME is the pathname of your home directory. The value of HOME is used primarily by the cd command as the default directory when you specify no argument. It also is used by a great many commands and shell scripts. The variable is initialized by the UNIX logon procedure before any shell is invoked. It almost is never proper for you to change the value of HOME. The default value of HOME is the sixth part of the /etc/passwd file entry for your logon name.
IFS IFS with the Korn shell works the same as it does with the Bourne shell. The value of IFS is zero or more characters to be treated by the shell as delimiters when parsing a command line into words or using the read command. The first character of IFS is used by the shell to separate arguments for the $* variable. Rarely manipulated at the keyboard, the IFS variable can be altered in a shell script to parse a string into substrings using arbitrary delimiters. Improper alteration of the IFS variable can cause bizarre problems, so you always should manipulate it with care and always restore it to its original value. The default value of IFS consists of the three characters Blank, Tab, and Newline in succession.
LINES The value of LINES is an integer number representing the number of lines displayed by your terminal. The Korn shell uses the value of LINES, if set, to limit the printing of select lists (see "Using the select Statement," later in this chapter). If no value is set, select lists can be arbitrarily long, and some lines may scroll off the display. There is no default value for LINES.
LOGNAME The logon name of the user as mentioned in the user database /etc/passwd. Modification of this variable can upset some programs, so exercise caution when using it, and restore it back to its original value using it.
MAIL MAIL with the Korn shell works the same as it does with the Bourne shell. The value is the pathname of a file to be monitored by the shell for a change in its date of last modification. If a change is noted, the shell issues the message You have mail at the next opportunity. There is no default value for MAIL. You should set MAIL to the name of your mail file in your logon profile.
MAILCHECK The value of MAILCHECK is an integer number of seconds that specifies how often the shell should check for a change to the MAIL file. If MAILCHECK is not set or is zero, the shell checks at each command-line prompt for a change in the mail file. The default value of MAILCHECK is 600.
MAILPATH The value of MAILPATH is a colon-separated list of pathnames, each which identifies a file to be monitored for a change in the date of last modification. A pathname can be suffixed with a question mark and message to customize the You have mail message--for example, you can use MAILPATH=/var/spool/mail/jjv?New mail in /var/spool:/usr/mail/jjv?New mail in /usr/mail Generally, you should set the MAIL or the MAILPATH variable but not both. There is no default value for MAILPATH.
PATH PATH with the Korn shell works the same as it does with the Bourne shell. The default value is system dependent. This variable cannot be changed if the shell was started as a restricted shell.
PS1 PS1 is the primary prompt string. The Korn shell performs a full substitution on the value of $PS1 before displaying it at the beginning of each command-input line. You therefore can customize your prompt in the Korn shell environment to a much greater degree than when using the Bourne shell. Specify PS1='$PWD: ', for example, to make your prompt your current directory. (The quotes are important to prevent substitution of the value of PWD as part of the assignment; this enables the substitution to occur later when the value of $PS1 is printed.) You also can use an exclamation point (!) in the prompt that is replaced by the command number (see "Command History," earlier in this chapter). The default value is "$ ".
PS2 PS2 is the secondary prompt string. It is the same as with the Bourne shell. The default value is ">".
PS3 PS3 is the select prompt string. The value of $PS3 is printed as the selection prompt by the select command. (See "Using the select Statement," later in this chapter.)
PS4 PS4 is the debug prompt string. The value of $PS4 is scanned for variable substitution and is printed in front of each line displayed by the trace or -x option.
SHELL SHELL is the pathname of the shell. The Korn shell sets a default value for $SHELL only if it is not set when ksh begins. The value isn't used directly by the Korn shell, but many other commands (such as vi and pg) use the value of $SHELL as the pathname of the shell to be called when invoking a subshell. If the $SHELL variable is defined when ksh begins and starts with an r, the Korn shell behaves as a restricted shell. That is, the user cannot invoke commands with a full pathname, cannot use the cd command, and cannot modify the PATH variable.
TERM The value of TERM is a symbolic alphanumeric string that identifies the type of your terminal. Not used by the Korn shell directly, the variable name TERM is reserved for general system use. The proper setting of $TERM is important to the proper and reasonable operation of your terminal, and should be initialized appropriately when you log on. For the allowable values at your installation, consult your system administrator. There is no default value for TERM.
TMOUT The value of TMOUT is an integer specifying the number of seconds after which no terminal activity should cause the Korn shell to automatically log out. A value of zero disables the automatic logout function.
VISUAL The value of $VISUAL is used primarily by programs other than the Korn shell. If you set the value of VISUAL (in your profile or at the keyboard), however, the Korn shell will inspect the value for a pathname ending in vi, emacs, or gmacs. If one of these values is found, the Korn shell sets the corresponding vi, emacs, or gmacs option, enabling command editing. This is only a convenience. You still can toggle the Command-Edit mode by using the set -o command. There is no default value for VISUAL.


NOTE: I always put the following definition in my logon profile:
CDPATH=.:..:$HOME



			

The command cd src first looks for a directory named src as a subdirectory in the current directory. Failing that, the cd command looks for src in the parent directory. If no directory named src is found in either place, it tries to change to src in my home directory. I find that proper use of the CDPATH variable saves a lot of typing.


As with the Bourne shell, variable names in the Korn shell begin with a letter or an underscore, and they contain an arbitrary number of letters, underscores, and digits. The variable name is a symbolic representation for the variable's value, which can be changed by an assignment statement; by the set, read, or select statement; as a by-product of the execution of a built-in shell or other commands; or by the Korn shell itself. There is no arbitrary upper limit to the number of variables you can define and use, but the amount of memory available to the shell sets a practical (usually large) upper limit.

You can explicitly assign a value to a variable name by using an assignment in the format name=value. Note that you don't include a dollar sign ($) in front of name when you write the assignment. The dollar sign is appropriate only when referring to the value of the variable.

The value of a variable is a string--a sequence of alphanumeric and special characters--of arbitrary length. The Korn shell provides a number of extensions that enable the value of a variable to be manipulated by arithmetic methods. The variable's value still is stored as a string, however.

A variable retains its value from the time it is set--whether explicitly by you or implicitly by the Korn shell--until the value is changed or the shell exits. Note that the value isn't passed to commands and shell scripts that you invoke unless the variable is marked for exportation. You mark a variable for exporting with the typeset built-in shell command or the export alias. Alternatively, if the allexport option is switched on (by typing set -o allexport, for example), all variables created are exported automatically. Exported variables become part of the environment of all invoked commands.

Because the values of variables are retained internally in a memory table by the shell, all variables that the shell didn't inherit are lost when the shell exits. For this reason, you cannot assign a value to a shell variable inside a shell script--one invocation of the shell--and expect the value to be retained after the shell script exits; the shell returns to a higher level shell. In other words, you can assign values to variables and export the variables to pass values downward to subshells of your current shell, but you cannot pass values upward to higher level shells or shell scripts.

This limitation on the use of shell variables isn't normally visible to you at the keyboard. It generally arises in issues related to shell programming. However, if you invoke the shell directly (by entering the sh, ksh, or csh command) or indirectly (by entering the shell environment from within another UNIX command, such as vi or pg), you should realize that any changes to the shell environment, including variable settings and aliases, are lost when you return to your original shell level by exiting the subshell.

Referencing Variables

The Korn shell replaces strings that begin with $ and are followed by a reference expression appearing in command lines with the value of the reference expression. Any number of reference expressions may appear in the same command line. Adjacent references, when replaced, don't introduce new word boundaries into the command line. That is, a single word--a command name, option, or argument--isn't split into two or more words by replacement even if the replaced value contains blanks, tabs, or other delimiter characters. You can use the eval built-in shell command when you want delimiters in the replacement text to cause further word splitting.

Valid reference expressions for the Korn shell follow:
name {name#pattern}
{name} {name##pattern}
{name[n]} {name%pattern}
{name[*]} {name%%pattern}
{name[@]} {#@}
{name:word} {#*}
{name-word} {#name}
{name=word} {#name[*]}
{name?word} {#name[@]}
{name+word}

name The expression $name is replaced by the current value of the shell variable name. If no value for the variable has been defined, the dollar sign and the variable name are replaced with the null string. For example,

$ today="January 13"
$ print Today is:$today.
Today is:January 13.
$ print Today is $tomorrow.
Today is:.

{name} The expression ${name} is replaced by the current value of the shell variable name. The braces help to separate the variable reference from surrounding text; they are discarded after substitution. You must use braces to reference a shell parameter greater than $9--for example, ${10} or ${12}--or to reference an array variable. For example,

$ Person1=John
$ Person2=Mike
$ print $Person1 and $Person2
John and Mike
$ print $Person1and$Person2
Person1and: not defined
$ print ${Person1}and$Person2
JohnandMike

{name[n]} The value }of the expression is the value of the nth element of the array variable name; it is null if the nth element isn't set. The first element of an array variable is ${name[0]}. For example,

$ set -A words hello goodbye
$ echo $words[1]
hello[1]
$ echo ${words[1]}
goodbye
$ echo $words
hello

{name[*]} The value }of the expression is the value of all the elements of the array variable name that are set, separated by blanks. Substitution occurs in the same way as for the special expression $* with regard to embedded blanks and word splitting. For example,

$ set -A planets Mercury Venus Earth Mars
$ planet[9]=Pluto
$ print ${planets[*]}
Mercury Venus Earth Mars Pluto

{name[@]} The value of} the expression is the value of all the elements of the array variable name that are set, separated by blanks. If elements of the array contain strings with embedded blanks and if the expression ${name[@]} is contained inside quotes, the number of words in the substituted expression is equal to the number of non-null array elements. Otherwise, embedded blanks cause word splitting to occur, and the number of substituted words will be greater than the number of non-null array elements. For example,

$ set -A committee "B Jones" "M Hartly" "C Rogers"
$ for word in ${committee[@]}
> do
> print $word
> done
B
Jones
M
Hartly
C
Rogers
$ for word in "${committee[@]}"
> do
> print $word
> done
B Jones
M Hartly
C Rogers}

{name:-word} The }expression is replaced by the value of variable name, if the variable has a value and the value consists of at least one character. Otherwise, the expression is replaced by word. Note that word should not contain embedded blanks or tabs, although it may contain quoted strings.

Combine : with -, =, ?, or + to treat a variable with a null value (that is, a zero-length string) the same as an unset variable. Without :, the variable is tested only for whether it is set. For example,

$ month=January
$ print This month is ${month:-unknown}
This month is January
$ print This year is ${year:-unknown}
This year is unknown

{name-word} The expression} is replaced by the value of name, if the variable has a value. Otherwise, it is replaced by word. You can use ${name:-word} to ignore a value that is not set or is null. For example,

$unset month
$ month=January
$ print This month is ${month-unknown}
This month is January
$ print This year is ${year-unknown}
This year is unknown

This may look similar to the previous expression, {name:-word}, so to clarify, look at this example:

$ unset month
$ month=""
$ echo ${month-unknown}

$echo ${month:-unknown}
unknown

{name=word} The }expression is replaced by the value of name, if the variable has a value. Otherwise, word is assigned as the value of name, and the expression is replaced by word. You can use ${name:=word} to assign word to name if the variable is not set or is null. For example,

$ print This month is $month.
This month is .
$ print This month is ${month=January}.
This month is January.
$ print This month is $month.
This month is January.

{name?word} The }expression is replaced by the value of name, if the variable has a value. Otherwise, the string word is printed as an error message. An unset variable is recognized as an error and halts processing of the current command line. If the error is recognized inside a shell script, execution of the shell script is terminated. Use ${name:?word} to recognize an unset or null value as an error. word can be omitted from the expression; if it is, a standard error message is displayed. For example,

$ month=January
$ print This month is ${month?unknown}
This month is January
$ print This year is ${year?unknown}
ksh: year: unknown
$ print This year is ${year?}
ksh: year: parameter null or not set

{name+word} The expression} is replaced by the value of word if the variable name has a value. If the variable is not set, the expression is replaced by the null string. That is, if name has a value, it temporarily treats the value as though it were word. If name doesn't have a value, the expression has no value either. Use ${name:+word} to treat a null value the same as an unset value. For example,

$ month=January
$ print This month is ${month+unknown}
This month is unknown.
$ print This year is ${year+unknown}
This year is .

{name#pattern} The value }of the expression is the value of name with the leftmost occurrence of pattern deleted. The shortest match for pattern is recognized. For pattern, specify a string that contains any character sequence, variable and command substitutions, and wildcard expressions. Only the first occurrence of pattern is deleted. For example,

$ print $PWD
/usr/home/valley
$ print ${PWD#*/}
usr/home/valley

{name##pattern} The value of the expression is name with anything to the left of the longest match of pattern removed. For example,

$ print $PWD
/usr/home/valley
$ print ${PWD##*/}
valley

{name%pattern} The value of the expression is the value of name with the shortest rightmost string matching pattern deleted. For example,

$ print $FNAME
s.myfile.c
$ print ${FNAME%.*}
s.myfile

{name%%pattern} The value of the expression is the value of name with the longest rightmost string matching pattern deleted. For example,


$ print $FNAME
s.myfile.c
$ print ${FNAME%%.*}
s

{#@} The value of the expression is the integer number of arguments that would be returned by $@. {#*} The value of the expression is the integer number of arguments that would be returned by $*. It is the same as $#. {#name} The value of the expression is the length of the string value of variable name. For example,

$ print $FNAME
s.myfile.c
$ print ${#FNAME}
10

{#name[*]} The value of the expression is the number of elements of the array variable name that are set. For example,

$ set -A planets Mercury Venus Earth Mars
$ print ${#planets[*]}
4

{#name[@]} {#name[@]} is the same as {#name[*]}.

Array Variables

An array variable is a variable with more than one value. Array variables are helpful for managing lists of strings, because you can reference an individual element in the list without resorting to string-splitting techniques.

You can assign values to an array one at a time by using the assignment statement. For example,

$ planets[1]=Mercury
$ planets[2]=Venus
$ planets[3]=Earth
$ print ${planets[2]}
Venus

The general syntax name[subscript] is supported by the Korn shell for referring to elements of an array. For subscript, supply an integer number in the range of 0 through 511, or write a variable expression with the value of the desired element number. Element numbers begin at zero. Thus, the first element in an array is ${name[0]}.

You can use the -A option of the set command to set many array elements with one statement. For example, the preceding code could be rewritten as this:

$ set -A planets Mercury Venus Earth
$ print ${planets[2]}
Venus

You also can substitute all the elements of an array by using the special notation ${name[*]} or ${name[@]}. For example,

$ set -A planets Mercury Venus Earth
$ planets[9]=Pluto
$ planets[7]=Uranus
$ print The known planets are: ${planets[*]}
The known planets are: Mercury Venus Earth Uranus Pluto

You should remember a few points when using array variables:

$ print $planets
Mercury

			
			
$ print There are ${#planets[*]} planets: ${planets[*]}
There are 5 planets: Mercury Venus Earth Uranus Pluto

			
			
$ print The known planets are $planets[*]
The known planets are Mercury[*]
$ print The second planet from the Sun is $planets[2]
The second planet from the sun is Mercury[2]

			

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 such as expr. The Korn shell adds some built-in capabilities 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 performing repetitive calculations, even with the typeset statement. Floating-point--real numbers with decimal points, fractions, and so on--calculations aren't supported. Therefore, all your calculations must use integer values, and they will yield integer results. The shell arithmetic is sufficient to support programming concepts such as loop control with counters, however.

The typeset statement is an extension provided by the Korn shell to permit some amount of control over the format and use of shell variables. When typeset is used for managing variables, its syntax follows:

typeset [ +/-HLRZilrtux [n] ] [ name[=value] ] ...

The particular set of options 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 options listed in Table 11.12 should be specified.

Table 11.12. typeset options.

Option Description
-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 then can 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.
-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 always is 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 also can specify the -L, -LZ, -R, or -RZ option 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 with -u. You must specify at least one name argument, and you can provide an optional initial value for some or all of the named variables.
-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 can 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 named variable(s) is treated as read-only, meaning that subsequent assignments of a value to the named variables are 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 with any of the other options.
-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 can provide an optional initial value for some or all of the named variables. Don't specify the -L or -LZ options 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.
-u The value of the named variable(s) should be converted to all uppercase letters when it is substituted. Don't specify this option with -l. You must specify at least one name argument, and you can provide an optional 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 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 aliases) 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 as exportable.
-Z Same as -RZ.

Apart from exporting variables, usually by way of the export alias, the typeset command is used mainly for two purposes:

Although the Korn shell doesn't require that a variable be declared as an 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. Similarly, there are contexts in which the shell recognizes 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 that is equivalent to typeset -i:

alias integer="typeset -i"

You can use the alias to make your integer definitions more readable, as in this revision:

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 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 easily can generate the required filenames by using code such as this:

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:

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 by 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. In radix 16 (hexadecimal), for example, the digits are 0 through 9 and A through F.

Table 11.13 shows the arithmetic operators supported by the Korn shell for use in arithmetic expressions.

Table 11.13. Arithmetic operators in the Korn shell.

Operator Expression Value of Expression
- -exp Unary minus--the negative of exp
! !exp 0 when exp is non-zero; otherwise,
~ ~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,
>= exp1 >= exp2 1 if exp1 is greater than or equal to exp2; otherwise,
< exp1 < exp2 1 if exp1 is less than exp2; otherwise,
> exp1 > exp2 1 if exp1 is greater than exp2; otherwise,
== exp1 == exp2 1 if exp1 is equal to exp2; otherwise,
!= exp1 != exp2 1 if exp1 is not equal to exp2; otherwise,
& 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,
|| exp1 || exp2 1 if exp1 is non-zero or exp2 is non-zero; otherwise,
= var = exp Assigns the value of exp to Variable ID
+= var += exp Adds 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.

The operators in Table 11.13 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 first by multiplying b*y, and then by 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 first by adding a and b, and then by multiplying the sum by y.

The let command is a built-in shell 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. 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, it causes execution of the then clause--and a command setting a non-zero exit value is false--that is, it causes execution of the else clause.

Because of the let command's inverted exit value, for example, 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:

Many Korn shell users employ the convention of always quoting an expression in its entirety, so they avoid the problem of shell metacharacters entirely.

Take another look at the syntax of the let command. Notice that each of its terms is an arbitrary expression. 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 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 this 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, because 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, as well as in a number of other places. Unlike the let command, however, the (( )) syntax permits only one expression between the doubled parentheses.

There is also a version of (( )) that returns the string representation of the calculation; this is $(( )). In this form, the result is returned to the shell. For example,

$ echo "(( 4+5 ))"
(( 4+5 ))
$ echo "$(( 4+5 ))"
9

You can use arithmetic expressions in any of these contexts:

Practical Examples of Arithmetic Now that you have reviewed all the basics of arithmetic in the Korn shell, you should take a look at some specific examples. This is an example of how not to use arithmetic expressions, for example:

$ x=4 y=5
$ print x+y
x+y

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 effect 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 fails 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 perform a simple arithmetic problem at the keyboard, and I would tend to agree with you. At the keyboard, a more effective approach is simply to use the expr command. For example,

$ expr 4 +
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 declared previously as 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
16#7d

Here, the variable hex is declared to be an 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. 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--converting from hexadecimal to decimal--is just as easy:

$ integer n
$ n=16#7d
$ print $((n))
125

At the keyboard, after you declare the hex and n variables, they remain in effect indefinitely. You can use them repeatedly to convert between hexadecimal and decimal. For example,

$ hex=4096; print $hex
16#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, which makes the Korn shell an attractive environment for program development. This section reviews 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 9 pertains equally to the Korn shell and isn't repeated here.

The Korn shell extensions useful for writing shell scripts are conditional expressions, which enhance the flexibility of the following:

If you are going to be writing shell scripts that will be used by many people, it is wise to place this on the first line of the script:

#!/bin/ksh

This tells the user's shell under which shell the script actually should run under. Running a Korn shell script under the C shell, for example, just won't work no matter how hard you try!

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 in the following sections.

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 also can write an extended conditional test expression as the argument of if, while, or until. A conditional test expression has this general form:

[[ conditional-exp ]]

where conditional-exp is any of the forms shown in Table 11.14.

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.

Table 11.14. Conditional expressions.

Expression Bourne Shell Condition When True
-r file Yes File exists.
-w file Yes 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 is mounted as read-only.
-x file Yes File exists and has Execute permission set. The file might not actually be executable. Directories usually have the Execute permission flag set.
-f file Yes File exists and is a regular file.
-d file Yes File exists and is a directory.
-c file Yes File exists and is a character-special file.
-b file Yes File exists and is a block-special file.
-p file Yes File exists and is a named pipe.
-u file Yes The Set User ID permission flag is set for file.
-g file Yes The Set Group ID permission flag is set for file.
-k file Yes The Sticky permission flag is set for file.
-s file Yes File has a size greater than zero.
-L file No File is a symbolic link.
-O file No File has an Owner ID equal to the effective User ID of the current process.
-G file No File has a Group ID equal to the effective Group ID of the current process.
-S file No File is a socket.
-t [ fildes ] Yes The file descriptor fildes--whose default is 1--is a terminal.
-o option No The named option is set.
-z string Yes string is a zero-length string.
-n string Yes string is not a zero-length string.
string Yes string is not a zero-length or null string.
string = pat Yes string matches the pattern pat.
string != pat Yes string does not match the pattern pat.
s1 < s2 No String s1 is less than string s2. That is, pat collates before s2.
s1 > s2 No String s1 is greater than string s2. That is, pat collates after s2.
file1 -nt file2 No File file1 is newer than file file2.
file1 -ot file2 No File file1 is older than file file2.
file1 -ef file2 No File file1 is the same file as file file2.
e1 -eq e2 No Expressions e1 and e2 are equal.
e1 -ne e2 No Expressions e1 and e2 are not equal.
e1 -gt e2 No Expression e1 is greater than e2.
e1 -ge e2 No Expression e1 is greater than or equal to e2.
e1 -lt e2 No Expression e1 is less than e2.
e1 -le e2 No 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 alternative 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 the -f option to manage functions. The -f option has four forms, which are listed in Table 11.15.

Table 11.15. -f option forms.

Form Description
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 the 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. A function cannot be exported to another instance of ksh, however. 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; however, 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; 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 that after an autoload function is 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 used heavily.

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 used often, but it is useful particularly 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 logon 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 logon 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 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 identify the invalid letter.

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 usually is 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. Instead, 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 11.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.

Listing 11.1. Scanning options with getopts.

# 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 11.1 is executable. Enter the statements into a file and mark the file executable with chmod +x filename. 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.

You should note two special points about Listing 11.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 simply can provide your own error message for any unrecognized option letter.

If the option string doesn't begin with :, getopts provides its own error handling. After 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. After identifying options with getopts, however, you don't want to see them again when you examine the remaining positional arguments. You must throw away the option words 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 recognized as a variable reference; you don't need to include 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 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 follows:

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 1. 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 that 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 lists 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 11.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 is contained is terminated with the exit built-in shell command.

Listing 11.2. Implementing a menu system with select.

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. 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 in Listing 11.3.

Listing 11.3. Basic Process Communication.

 (echo "/^Payroll
+1
i"
cat newlist
echo "."
echo "w"
echo "q"
) | ed - paylist

This form of intertask communication is sufficient if you just need to pass some data to another command or to read its output. Suppose that in Listing 11.3, though, you want 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, -p tells read to read from the coprocess pipe.

Using these facilities, you could rewrite the preceding procedure as the one shown in Listing 11.4.

Listing 11.4.Process Communication Using Coprocessing.

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
          ;;
    esac
done

You should note the following in this example:


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, program 11.4, which uses coprocessing, is more complicated than program 11.3, 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 11.4 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 do not give you a 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 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 instead of execute another program.)

Similarly, the following code reassigns the pipe for reading from the coprocess:

exec 4<&p

If you place these two 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.

Cautionary Tales

The Korn shell is a very powerful shell to script with; however, it has its problems. One of the more obscure problems involves piping. Consider this script:

person=noone
echo At start: $person
who | while read person tty junk
do
    echo $person is logged on at terminal $tty
done
echo At end: $person

What will be the value of person after you run this script? The answer is you don't know--you can't know. This script gave me two different results on two different implementations of the Korn shell. On one system, person was an empty (null) string. On the other system, it contained noone.

The reason for this unpredictability is that you're piping the output into another command. When you use a pipe, you effectively start another shell to manage the output. Different implementations may carry out the piping in a different way, though, because while and read are internal to the shell, so there is no need to start a second shell to manage them.

Don't write a scripts that work under one implementation of a shell perfectly. Little bugs like this can creep in and render your script unusable. Create safeguards against this by saving variables and restoring them. One day, your script actually might be needed on a different system, and the last thing you want is lots of people asking you why it won't work.

Customizing the Korn Shell

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 that you might find another user's logon environment so foreign as to be almost unusable by you. Indeed, some places try to place a limit on user customization.

You can adapt the Korn shell to your preferred way of working in many ways. Of course, keep in mind that if you're a beginning UNIX user, you might not have many preferences. As your familiarity with UNIX and the Korn shell increases, you'll find many conveniences, shorthand methods, and customary uses that seem comfortable to you. The Korn shell helps you along by enabling you to encapsulate favorite behaviors into your logon profile script and elsewhere.

Customizing the Korn shell begins with your logon profile script, which is named .profile and resides in your home directory. The file $HOME/.profile is of special importance, because the Korn shell executes it every time you log on--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 logon. 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. Your script is specific to your logon name and home directory. Altering it conceivably could affect only those people who have your password and can log on with your logon 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. 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 logon 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 think that mastery of them is essential to writing good profile scripts, though. 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 file. You also should observe the order in which these items 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 this:

stty erase '^H' kill '^U' intr '^C'

This example uses the caret (^) in front of an upper- or lowercase 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; a lack of quotes causes improper interpretation of the stty command. (For details about the stty command, see your UNIX User's Reference Manual.)

Controlling Resources with ulimit

Using ulimit to control resources can be a handy feature, especially if you are a system administrator. Although UNIX comes with a ulimit command, the Korn shell offers its own alternative. The syntax for ulimit follows:

ulimit [-HSacdfnstv] [limit]

The H and S flags tell ulimit that you are defining a hard or soft limit. A hard limit cannot be increased after it is set. A soft limit can be modified up to the value of the hard limit. If both H and S are omitted, the specified limit is applied to both the hard and soft limits.

If limit is omitted, the current value of the specified limit is displayed. If ulimit is invoked with no options, it returns the number of blocks that can be written by a process (the same as typing ulimit -f). Table 11.16 lists the ulimit parameters.

Table 11.16. The ulimit parameters.

Parameter Function
-a Lists all resource limits
-c Specifies the number of blocks for a core file
-d Specifies the number of kilobytes for the data area
-f Specifies the number of blocks that may be written to a file
-n Specifies one more than the number of files that may be open at once
-s Specifies the number of kilobytes for the stack area
-t Specifies the number of seconds that may be used by each process
-v Specifies the number of kilobytes for virtual memory


TIP: Unless you are going to be doing a lot of programming, it is useful to place ulimit -c 0 in your profile. This prevents any program that crashes from creating a core file, so it also saves disk space. Many core files can be megabytes in size, so any way of reducing them is often a welcome method!

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 variables. 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 preceding 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 set by the UNIX logon processor. Your system administrator also can provide a logon script to set values before your .profile script runs. The PATH and MAIL variables usually have initial values already set when your script starts, for example. 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 which mail subsystems are in use.

Setting Local Variables for Shell Control

Local variables are variables the shell uses but aren't 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 also might 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 you like to use. You must invent your own aliases; each user tends to have a different set. Most users make up some aliases for the ls command. You even can redefine the default behavior of the ls command by defining an alias named ls. Here are some typical aliases 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 mail='/usr/bin/mailx'
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 you like to use, including autoload functions. I use some function definitions as keyboard shorthand, because a function can do things an alias can't. You might want to use the go function described earlier in this chapter, for example, for switching directories.

Setting Shell Options

If you find yourself frequently setting the same shell options at the command line, you can set them in your .profile instead. To set the preferred shell options, use the set command. If you prefer to use 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 Logon

Execute commands you like to run every time you logon. You might want to run the who command to find out who's currently logged on, for example. Similarly, df, which isn't present on all UNIX systems, displays the amount of free disk space available on mounted file systems.

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 on. 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 usually is 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 you designate by assigning its pathname to the ENV variable. The shell 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 logon 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 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 becomes effective the next time you log on. It becomes effective immediately if you test your .profile with the following . command:

. .profile

Commands you want to put in your ENV file include alias definitions and shell options. You may prefer them in here instead of .profile to be sure of always getting a shell that looks and acts the same way each time.


TIP: A very useful if statement to put in your ENV file follows:

if [[ -o interactive ]]
then
    ....
    insert your ENV lines in here.
    ....

fi

Any lines placed inside the if statement are executed only if the shell is to be interactive--that is, it gives you a prompt at which you can type commands. This can cut down on the overhead of processing a new shell many times if the shell is being called with a command line that will run a command--for example,

ksh -c ls -l

If you have a lot of aliases and/or functions, it might be a good idea to place these in a separate file again and call this file from ENV to set them up. In my ENV file, I have these two lines:

. .ksh_alias
. .ksh_funcs

In .ksh_alias, I've placed all my alias definitions, and in .ksh_funcs, I've placed all my function definitions. This shortens my ENV file substantially and makes everything look a lot neater.


Adding Settings for Other Programs to Your .profile

Customizing your environment doesn't stop with using the logon 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. 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 logon profile to establish your preferred vi settings. Many UNIX commands respond to environment variables, which enables you to customize these commands in your logon profile.

Controlling Jobs

The idea of a job might be somewhat foreign to UNIX users, because 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 always has 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. The command cc myprog.c &, for example, 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 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 are an operating system service, not a Korn shell service--you still can 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 you currently have running. The wait, kill, bg, and fg commands also can refer to background jobs by their Process ID, which you generally can obtain from the output of the ps command. The use of Korn shell job numbers is preferred, however, because they are simpler and safer to use than Process IDs.

You create jobs in one of three ways:

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.

Table 11.17 shows the full syntax of the % argument accepted by the wait, kill, fg, and bg commands.

Table 11.17. Job reference argument syntax.

Syntax References
%number The job number
%string The job whose command begins with string
%?string The job whose command contains string
%% The current job
%+ The current job (also %%)
%- The preceding job

The syntax of the Korn shell job-control commands is summarized in the following sections.

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, they display information only for the specified jobs. For job, specify a Process ID or a job reference beginning with %. To find out whether job 2 from the preceding 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 generally is 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 follows:

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. (See 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 which names 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.

Suppose that 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 follows:

wait [ job ... ]

For job, specify one or more Process ID numbers or job references that designate the job or jobs for which you want to wait. 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 repeatedly until the job or jobs you want were marked Done.

One situation in which the wait command is useful is when developing some formatted text files. You might 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. 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 Listing 11.5.

Listing 11.5. A console session.

$ 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 listing, you overlapped the editing of chap2.nr with the formatted printing of chap1.nr. After finishing the edit of chap2.nr, you see by running the jobs command that both nroff jobs still are running. Because 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
$

Another useful application of wait is managing X sessions. When you log on and use X, one of two files is processed: .xinitrc or .xsession. The file processed depends on the method you used to run X. When I connect, my .xsession file gets processed. When .xsession terminates, my X session is finished and I get logged out. An extract from my .xsession looks like this:

ctwm &
WINM=$!
xv -quit -root etc/pics/space.gif &
xterm -sb -sl 2000 -ls -title "Xterm 1" -geometry 80x24+0+86 &
xterm -sb -sl 2000 -ls -title "Xterm 2" -geometry 80x24+523+430 &
wait $WINM

This code uses two features of the Korn shell. First, I use $! to get the Process ID of the Window Manager I run, ctwm, and assign it to the variable WINM. I then start two xterms and set the desktop background by using xv. Then I issue a wait command that waits for the process $WINM to finish. In this case, WINM is the Process ID of the Window Manager, so, in other words, after my Window Manager shuts down, my .xsession is terminated and I get logged out.

Moving Background Jobs into the Foreground Use fg to move background jobs into the foreground. Foreground execution implies interactive processing 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 follows:

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 you most recently stopped or started.

The need to use the fg command often arises as a result of actions you take yourself. Suppose that 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, 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 the 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 immediately switches back to the Stopped state if it requires terminal input. The syntax for bg follows:

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 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 need to use Ctrl+Z to stop (suspend) the foreground job.

After you stop the job and have a shell prompt, you need to decide what to do with the job you stopped. You can perform other tasks and restart the stopped job with the fg command when finished, as described earlier. But if the job you stopped is not interactive (if it can run without constant input from you), you can tell the shell to restart the job but leave it in the background.

Suppose that you start 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 that 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 the Bourne shell, only the features special to the Korn shell were discussed here.

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. The Korn shell 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 initially was offered only in the BSD variant of UNIX. The Korn shell ported its extensions, 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 enables you to capture in a disk file each command as you execute it. The file is preserved across logons so that you have some of the context of your previous session when you next log on. 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 written originally, 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 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. 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 the vi or EMACS Command-Edit 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 feature greatly reduces the amount of typing needed for frequently executed commands. It 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 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 the convenient processing of lists. Integer variables defined with typeset, the let command, and the (( )) expression notation enable you to perform basic numeric calculations without having to leave the shell environment. You no longer have to resort to command substitution for the expr or bc command.

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 even can 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 an 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 User ID and Set Group ID 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. Remember to exercise caution, though: Badly written scripts can give a potential attacker a door to a more privileged user.

Last but not least, the Korn shell provides a way of getting around the problem of not being able to export aliases and functions. By 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 the Korn shell for your keyboard environment, which is a step up for sure!

TOCBACKFORWARDHOME


©Copyright, Macmillan Computer Publishing. All rights reserved.