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:

  • Command aliases. Aliases enable you to abbreviate frequently used commands without resorting to shell programming, thus improving your overall keyboard productivity.
  • Command history. You can use command history alone or with command editing to modify and reuse previously typed commands. You also can use command history as a log of keyboard actions.
  • Command editing. The Korn shell provides two styles of command editing that enable you to revise and correct commands as you type them. Command editing can greatly reduce the amount of time you spend retyping commands.
  • Directory management. The Korn shell provides extensions to the cd command, new pathname syntax, and new shell variables to facilitate switching between directories and to abbreviate long pathnames.
  • Arithmetic expressions. The Bourne shell offers minimal arithmetic capabilities. The Korn shell offers much greater power for handling numbers, even though a hand-held calculator is still a better tool for calculations.
  • Syntax improvements. The Korn shell offers improvements in the syntax of the if statement, the built-in test command, and the command substitution expression, which can improve the power and readability of your shell scripts.
  • Wildcard expressions. The Korn shell provides more wildcard formats to reduce your typing workload.
  • Coprocessing. The conventional pipe of the Bourne shell is expanded to permit more flexible, programmed interaction between your shell script and the commands you invoke.
  • Job processing. The Korn shell includes batch job monitoring features to simplify running processes in the background and to enable you to perform more tasks simultaneously.
  • Privileged mode switching. The Bourne shell provides no special features to capitalize on the Set User ID capability of UNIX. The privileged mode of the Korn shell, on the other hand, enables you to switch the Set User ID mode on and off and to develop procedures as shell scripts that previously required C language programming.

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:

  • Redirecting input and output. The Bourne shell redirection operators <, <<, >, and >> and the here document facility (<<label) all have identical syntax and work the same way. A here document is a way of inserting a block of text into a script that you can redirect into another process or to a file. For example:
$ 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.
!
  • Would display on screen as:
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
  • Entering multiple commands on one line. The semicolon (;) marks the end of a shell statement. To enter multiple commands on one line, simply end each command except the last with a semicolon.
  • Supporting filename substitutions. The Korn shell supports the familiar substitution characters (also known as wildcards) *, ?, and [...]; when used in a word, these characters cause the word to be replaced with all matching filenames. The Korn shell also supports additional filename matching patterns that have the general form *(expression) and the tilde (~) abbreviation, but you don't need to use these extensions. For example, if you had three files, say time.x, time.y and tame.x, then time.? will match time.x and time.y. t* will match all three files, and t[ai]me.x will match time.x and tame.x. The *(expression) wildcard is similar in principle to [...]; however, unlike [...], the new wildcard *(expression) can specify longer patterns. Also wildcards can be embeddedd in it, so you could end up with a wildcard expression that looked like: file*([123].*(doc|txt)|0?.doc), which would match the following names: file1.doc, file1.txt, file2.doc, file2.txt, file03.doc, file0d.docÉ. The complete list would be too long to display here, but you can see the power of the new wildcard.
  • Substituting variables. The Korn shell supports the variable substitution form $name, as well as all the special variable references $*, $@, $$, $-, $?, and the parameters $0 through $9. The ${name} special form and the ${name[op]text} form are supported with their usual meanings. In addition, the Korn shell supports ${name[index]} array variables, $(...) special command substitutions, and others. The extensions do not conflict with Bourne shell syntax, and you do not need to use them.
  • Substituting commands. The Bourne shell command substitution form, 'command', is fully supported in the Korn shell, with the same syntax and behavior as the Bourne shell format. The Korn shell also supports the variant syntax, $(...), to simplify the use of command substitutions.
  • Recognizing escaping and quoting. The Korn shell recognizes quoted strings of the form, "..." and '...', with the same meaning and effect. A single special character can be deprived of its meaning with the backslash (\); the backslash is removed from the generated command line, except when it appears within single quotes. There are no extensions to the standard escaping and quoting techniques.
  • Extending a command over multiple lines. To extend a command over multiple lines, end the line with a backslash (\). The backslash must be the last character of the line. The combination of the backslash, followed immediately by a newline character, is recognized and simply deleted from the command input. This is the same behavior as the Bourne shell.

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:

  • View the commands in the history list by using the history command. Use the history command when you can't remember whether you've already performed an action or if you want to refer to the syntax or operands of a previous command.
  • Resubmit a command from the list by using the r command. Except for very short commands, it's faster to resubmit a command you typed before with the r command than it is to type the command again. The r command provides several alternative ways for you to identify which command in the history list you want to reexecute. You can also modify a command in the history list and then execute the modified command.
  • Modify a command in the list using the fc command. You can use any text editor you want to edit the chosen command. By default, the Korn shell invokes the crusty old ed command for you, but you can change the default to any text editor you want by changing the value in the FCEDIT variable. The modified command will execute immediately after you leave the editor.

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:

  • To process the command you most recently entered--other than fc, of course--omit both first and last.
  • To select and process only one command, specify the command as the value of first and omit last.
  • To select a range of commands, specify the first command in the range with first and specify the last command in the range with last.
  • To designate a command by its line-number position in the history list, use a plain number--for example, 219.
  • To designate a command preceding the most recent command in the history list, use a negative number. In this command-history list, for example, the command fc -2 selects the vi command:
135 mkdir paywork
136 mv paymast/newemps paywork
137 cd paywork
138 vi newemps
139 payedit newemps
  • To select a command by its name instead of by its position in the history list, use a command name or any prefix of a command name. The most recent command line that begins with the string you specify will be selected. In this command-history example, you also could select the vi command by entering fc vi.

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
hellods
hellods
hellods
hello{{{{e 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:

                      

 
                         
·                If you reference the array variable without a subscript, the value of the reference
·                                               is the first element of the array:
·                                       
 
                       
                               
$ print $planets

  
                         Mercury
                 
 
                       
 
                       
 
                         
·                Array variables cannot be exported.
·                                               

·                 

                        

·                The special expression ${#name[*]} or ${#name[@]}
·                                               can be used to get the number of non-null elements in an array. For example,
·                                       
 
                       
                               
$ print There are ${#planets[*]} planets: ${planets[*]}

  
                         There are 5 planets: Mercury Venus Earth Uranus Pluto
                 
 
                       
 
                       
 
                         
·                You must use the brace-enclosed expression syntax to refer to elements of an
·                                               array. Without the braces, the Korn shell interprets the expression in the same way
·                                               the Bourne shell would. For example,
·                                       
 
                       
                               
$ 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:

                      

 
                         
·                Setting up variables that you plan to use for calculation as integer variables
·                                               

·                 

                        

·                Defining special formatting options for variables
·                                       
 
                       

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:

                      

 
                         
·                let "prod=x|y"
·                                               

·                 

                        

·                let prod="x|y"
·                                               

·                 

                        

·                let prod=x\|y
·                                       
 
                       

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:

                      

 
                         
·                As an array subscript
·                                               

·                 

                        

·                As arguments of the let command
·                                               

·                 

                        

·                Inside doubled parentheses (( ))
·                                               

·                 

                        

·                As the shift count in shift
·                                               

·                 

                        

·                As operands of the -eq, -ne, -gt, -lt, -ge,
·                                               and -le operators in test, [, and [[ commands
·                                               

·                 

                        

·                As resource limits in ulimit
·                                               

·                 

                        

·                As the right-hand side of an assignment statement, but only when the variable
·                                               name being assigned was defined as an integer variable with the typeset
·                                               or integer statement
·                                       
 
                       

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, while, and until statements
·                                               

·                 

                        

·                Array variables, integer variables, extended variable reference expressions,
·                                               and arithmetic expressions
·                                               

·                 

                        

·                A new select statement for constructing a menu of prompts from which
·                                               the user can select a choice
·                                               

·                 

                        

·                Extended support for functions, including autoload functions
·                                               

·                 

                        

·                An enhanced form of the command expression $(...), which is simpler
·                                               to use than the backquoted form ´...´
·                                               

·                 

                        

·                Extended support for process communication withcoprocessing using the operator--|&.
·                                       
 
                       

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:

                      

 
                         
·                The exec command (exec 3>&p) is used to move the coprocess
·                                               input pipe from its default location to a numbered file descriptor.
·                                               

·                 

                        

·                The exec command (exec 4<&p) is used again to move the
·                                               coprocess output pipe to number file descriptor 4.
·                                               

·                 

                        

·                Subsequent read and print commands specify the file descriptor
·                                               as the source or destination of the operation, using the -u option.
·                                               

·                 

                        

·                Ordinary UNIX commands can write to the coprocess by redirecting to file descriptor
·                                               3 (cat filename >&3).
·                                       
 
 
 
                       
 
                 

 

 
  

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

  
 
  
 
 
 
                 
 
 
                       

Admittedly, 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.

                      

 
                         
·                Set control keys with the stty command.
·                                               
·                Set environment variables.
·                                               
·                Set local variables for shell control.
·                                               
·                Define aliases you like to use.
·                                               
·                Define functions you like to use, including autoload functions.
·                                               
·                Set your favorite shell options.
·                                               
·                Execute commands you want to run each time you log on.
·                                       
 
                       

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 explicitly designating a command for background execution with the &
·                                               operator
·                                               

·                 

                        

·                By switching a job into the background with the Korn shell bg command
·                                               

·                 

                        

·                By pressing the Suspend key--usually Ctrl+Z--while a foreground program is running
·                                       
 
                       

By convention, a job started or switched into the background continues to run

                       until it tries to read from your terminal. Then it is suspended by the operating

                       system until you intervene. When it is in this state, the jobs command shows

                       that the command is Stopped.

 
                       

A job that has been stopped usually needs to talk to you before it can continue.

                       In the previous jobs example, the vi command is shown to be stopped.

                       The command won't continue until you reconnect it to your terminal. You do this with

                       the fg command--for example, fg %3 or fg %vi. The vi

                       command then becomes the foreground process, and it resumes normal interactive execution

                       with you.

 
 
 
                       
 
                 

 

 
  

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

  
 
  
 
 
 
                 
 
 
                       

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!