![]() ![]() ![]() ![]() ![]() |
UNIX Unleashed, System Administrator's Edition
- 11 -Korn Shellby John Valley and Chris Johnson Chapter 8, "What Is a Shell," introduced the basics of UNIX shells, and Chapter 9, "The Bourne Shell," discussed the Bourne shell in particular. This chapter expands on the subject of shells by introducing the Korn shell--the second of the three main shell languages available to you. The third major shell language is discussed in Chapter 12, "The C Shell." The Korn shell is named after its author, David G. Korn of AT&T's Bell Laboratories, who wrote the first version of the program in 1986. Therefore, the Korn shell is a direct descendent of the Bourne shell. It is almost perfectly compatible with the Bourne shell; with a few minor exceptions, any shell script written to be executed by the Bourne shell can be executed correctly by the Korn shell. As a general rule, though, Korn shell scripts cannot be processed correctly by the Bourne shell. This upward compatibility provides a number of advantages--not the least of which is that it enables you to capitalize on your knowledge of the Bourne shell immediately. It also drastically reduces the amount of material you need to learn to begin using the Korn shell. Because the Korn shell is intended as a replacement for and an improvement on the Bourne shell, it is best discussed as a series of features added to the basic functionality of the Bourne shell. Many aspects of the shell's operation presented in Chapter 9, "The Bourne Shell," are not repeated here. Instead, this chapter summarizes the differences between the Bourne shell and the Korn shell. The list of Korn shell enhancements is extensive, ranging from the profound to the trivial. The most dramatic enhancements are those intended to facilitate keyboard interaction with the shell, but you also should be aware of many important extensions to shell syntax and shell programming techniques. The categories of enhancements follow:
Although you haven't been introduced to the C shell yet, you'll find that many of the Korn shell features duplicate those of the C shell, but with a different syntax. This is intentional. Although the C shell offers many desirable features, its general syntax is incompatible with the Bourne shell, making it somewhat of a square peg in a round hole in the UNIX world. The Korn shell solves this long-standing quandary in the UNIX world by offering the keyboard and shell programming features that people want, but in a form that is compatible with the old, well-established Bourne shell standard. Shell BasicsAs I mentioned earlier, the Korn shell is essentially a foundation equivalent to the Bourne shell with a new layer of goodies added on top. You can use the Korn shell as a one-for-one replacement of the Bourne shell, with no special knowledge of Korn shell features. Korn shell extensions do not come into play until you explicitly invoke them. In particular, the Korn shell is identical to the Bourne shell in the following areas:
$ cat <<-! This is a demonstration of a here document. As you can see the docuemnt uses the operator << to tell the shell that all the text on the next line to the label, in this case is !, is all to be read in and redirected to the cat command. This - tells the shell to remove leading tabs at the start of the line. !
This is a demonstration of a here document. As you can see the docuemnt uses the operator << to tell the shell that all the text on the next line to the label, in this case is !, is all to be read in and redirected to the cat command. This - tells the shell to remove leading tabs at the start
The general philosophy of the Korn shell is to invoke extensions and special features with syntax that is not legal for the Bourne shell. As a result, any commands and shell scripts that are syntactically correct for the Bourne shell will be interpreted identically by the Korn shell. All Korn shell extensions use syntactic forms that do not appear in the Bourne shell language. Features that are not invoked directly by commands, such as command history and command editing, are controlled instead by shell options. To use command editing, you first must issue the command set -o vi or set -o emacs. If you don't, the Korn shell command line works the same as the Bourne shell. Also note that the set command follows the general philosophy: set -o is not valid in the Bourne shell and generates a syntax error. The compatibility between the Bourne shell and Korn shell is nearly perfect; one of the design objectives of the Korn shell was that it be able to execute system-provided shell scripts written for the Bourne shell without the need to revise those scripts or to invoke the Bourne shell to run them. This objective meant that even minor idiosyncrasies of Bourne shell behavior could not be overlooked; the Korn shell design had to implement them all. The upshot of all this is that everything in Chapter 9 applies equally well, without restriction or caveat, to the Korn shell. Wildcard ExpressionsThe 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.
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.
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 SubstitutionAnother 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 CommandFor 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. AliasesThe 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 AliasesThe 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 AliasTo 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 DefinitionOne 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.
Using Exported AliasesThe 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 AliasesBy 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 OptionsBecause 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.
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.
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.
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 HistoryCommand 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:
Now take a closer look at these commands for viewing and manipulating command history. Displaying the Command-History ListThe 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
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.
Reexecuting a Command from the HistoryThe 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.
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: fcThe fc (fix command) command is a built-in Korn shell command. It provides access to the command-history list. Forms of the fc command enable you to display, edit, and reuse commands you previously entered. The Korn shell automatically defines the alias names history and r for you to reduce the amount of typing needed to perform simple history functions. The syntax of the fc command follows: fc [ -e editor ] [ -nlr ] [ first ] [ last ] When invoked with no options, the fc command selects a line from the command history using the values of first and last, invokes the default command editor, and waits for you to edit the command or commands selected. When you exit the editor, by filing the altered command text or by quitting the editor, the commands are executed. The fc command actually copies the selected commands to a temporary file and passes the file to the text editor. The contents of the file after editing become the command or commands to be executed. For example, if you enter the command $ fc vi where vi represents the value of first, the Korn shell copies the most recent vi command to a temporary file. The temporary file has an unrecognizable name, such as /usr/tmp/fc13159, and is located in a directory designated for temporary files. The file you actually edit is /usr/tmp/fc13159. Regardless of whether you change the text in file /msr/tmp/fc13159, the Korn shell executes its contents immediately after you exit the editor. You can specify the command or commands to be processed in the following manner:
135 mkdir paywork 136 mv paymast/newemps paywork 137 cd paywork 138 vi newemps 139 payedit newemps
The first and last command-line selectors don't have to use the same formats. You could select line 145 of the history list through the fifth-to-last line by entering fc 145 -5, for example. By default, the fc command invokes a text editor on the selected lines and reexecutes them after editing. You can modify this default behavior with the options shown in Table 11.6. Table 11.6. Options to modify the behavior of the fc command.
Command EditingCommand 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 ModeBefore 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 Modevi 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.
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.
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.
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.
EMACS Edit ModeThe 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.
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.
VariablesYou 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 VariablesVariables 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.
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.
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 VariablesThe 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 The expression $name is replaced by the current value of the shell variable name. If no value for the variable has been defined, the dollar sign and the variable name are replaced with the null string. For example, $ today="January 13" $ print Today is:$today. Today is:January 13. $ print Today is $tomorrow. Today is:. {name} The expression ${name} is replaced by the current value of the shell variable name. The braces help to separate the variable reference from surrounding text; they are discarded after substitution. You must use braces to reference a shell parameter greater than $9--for example, ${10} or ${12}--or to reference an array variable. For example, $ Person1=John $ Person2=Mike $ print $Person1 and $Person2 John and Mike $ print $Person1and$Person2 Person1and: not defined $ print ${Person1}and$Person2 JohnandMike {name[n]} The value }of the expression is the value of the nth element of the array variable name; it is null if the nth element isn't set. The first element of an array variable is ${name[0]}. For example, $ set -A words hello goodbye $ echo $words[1] hello[1] $ echo ${words[1]} goodbye $ echo $words hello {name[*]} The value }of the expression is the value of all the elements of the array variable name that are set, separated by blanks. Substitution occurs in the same way as for the special expression $* with regard to embedded blanks and word splitting. For example, $ set -A planets Mercury Venus Earth Mars $ planet[9]=Pluto $ print ${planets[*]} Mercury Venus Earth Mars Pluto {name[@]} The value of} the expression is the value of all the elements of the array variable name that are set, separated by blanks. If elements of the array contain strings with embedded blanks and if the expression ${name[@]} is contained inside quotes, the number of words in the substituted expression is equal to the number of non-null array elements. Otherwise, embedded blanks cause word splitting to occur, and the number of substituted words will be greater than the number of non-null array elements. For example, $ set -A committee "B Jones" "M Hartly" "C Rogers" $ for word in ${committee[@]} > do > print $word > done B Jones M Hartly C Rogers $ for word in "${committee[@]}" > do > print $word > done B Jones M Hartly C Rogers} {name:-word} The }expression is replaced by the value of variable name, if the variable has a value and the value consists of at least one character. Otherwise, the expression is replaced by word. Note that word should not contain embedded blanks or tabs, although it may contain quoted strings. Combine : with -, =, ?, or + to treat a variable with a null value (that is, a zero-length string) the same as an unset variable. Without :, the variable is tested only for whether it is set. For example, $ month=January $ print This month is ${month:-unknown} This month is January $ print This year is ${year:-unknown} This year is unknown {name-word} The expression} is replaced by the value of name, if the variable has a value. Otherwise, it is replaced by word. You can use ${name:-word} to ignore a value that is not set or is null. For example, $unset month $ month=January $ print This month is ${month-unknown} This month is January $ print This year is ${year-unknown} This year is unknown This may look similar to the previous expression, {name:-word}, so to clarify, look at this example: $ unset month $ month="" $ echo ${month-unknown} $echo ${month:-unknown} unknown {name=word} The }expression is replaced by the value of name, if the variable has a value. Otherwise, word is assigned as the value of name, and the expression is replaced by word. You can use ${name:=word} to assign word to name if the variable is not set or is null. For example, $ print This month is $month. This month is . $ print This month is ${month=January}. This month is January. $ print This month is $month. This month is January. {name?word} The }expression is replaced by the value of name, if the variable has a value. Otherwise, the string word is printed as an error message. An unset variable is recognized as an error and halts processing of the current command line. If the error is recognized inside a shell script, execution of the shell script is terminated. Use ${name:?word} to recognize an unset or null value as an error. word can be omitted from the expression; if it is, a standard error message is displayed. For example, $ month=January $ print This month is ${month?unknown} This month is January $ print This year is ${year?unknown} ksh: year: unknown $ print This year is ${year?} ksh: year: parameter null or not set {name+word} The expression} is replaced by the value of word if the variable name has a value. If the variable is not set, the expression is replaced by the null string. That is, if name has a value, it temporarily treats the value as though it were word. If name doesn't have a value, the expression has no value either. Use ${name:+word} to treat a null value the same as an unset value. For example, $ month=January $ print This month is ${month+unknown} This month is unknown. $ print This year is ${year+unknown} This year is . {name#pattern} The value }of the expression is the value of name with the leftmost occurrence of pattern deleted. The shortest match for pattern is recognized. For pattern, specify a string that contains any character sequence, variable and command substitutions, and wildcard expressions. Only the first occurrence of pattern is deleted. For example, $ print $PWD /usr/home/valley $ print ${PWD#*/} usr/home/valley {name##pattern} The value of the expression is name with anything to the left of the longest match of pattern removed. For example, $ print $PWD /usr/home/valley $ print ${PWD##*/} valley {name%pattern} The value of the expression is the value of name with the shortest rightmost string matching pattern deleted. For example, $ print $FNAME s.myfile.c $ print ${FNAME%.*} s.myfile {name%%pattern} The value of the expression is the value of name with the longest rightmost string matching pattern deleted. For example, $ print $FNAME s.myfile.c $ print ${FNAME%%.*} s {#@} The value of the expression is the integer number of arguments that would be returned by $@. {#*} The value of the expression is the integer number of arguments that would be returned by $*. It is the same as $#. {#name} The value of the expression is the length of the string value of variable name. For example, $ print $FNAME s.myfile.c $ print ${#FNAME} 10 {#name[*]} The value of the expression is the number of elements of the array variable name that are set. For example, $ set -A planets Mercury Venus Earth Mars $ print ${#planets[*]} 4 {#name[@]} {#name[@]} is the same as {#name[*]}. Array VariablesAn 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:
Variable ArithmeticAn 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.
Apart from exporting variables, usually by way of the export alias, the typeset command is used mainly for two purposes:
Although the Korn shell doesn't require that a variable be declared as an integer to do arithmetic with it, doing so provides some advantages. Calculations are more efficient when you use arithmetic variables in the let statement, because the shell can maintain the numeric value of the variable in an internal binary format, which is more suitable to the computer's math instructions. Similarly, there are contexts in which the shell recognizes arithmetic operators in an expression if the expression contains integer variables, but it won't if the expression uses standard variables. The general procedure for using typeset to define integer variables is straightforward. Before using variables for calculation, simply issue a typeset command to declare the variables as integers. For example, typeset -i x y sum read x y let sum=x+y print $sum The Korn shell automatically defines an alias named integer that is equivalent to typeset -i: alias integer="typeset -i" You can use the alias to make your integer definitions more readable, as in this revision: integer x y sum read x y let sum=x+y print $sum The second use of typeset--to set up output formatting options for variables--is of interest primarily to shell-script writers who want to generate nicely formatted output. The formatting options -L, -R, -LZ, and -RZ are also of some use in generating filenames. Suppose that you want to create a series of files that all end with a four-digit number. By writing the typedef statement typeset -Z4 suffix you easily can generate the required filenames by using code such as this: typeset -Z4 suffix=0 while ... do let suffix=suffix+1 print sampfile.$suffix done The Korn shell automatically right-justifies the value of $suffix in a four-character field and fills the number out to four digits with leading zeros. Thus, it generates the series of filenames sampefile.0001, sampfile.0002, and so on. Using let Use let to perform an arithmetic calculation. The syntax for the let statement, the second major element in the shell's support for arithmetic, is simple: let expr For expr, write an expression that consists of terms and operators. A term is a variable or a literal integer number--for example, 3 or 512. A literal integer number is assumed to be written in base 10. You can specify another base by using the format radix#number, where radix is the number base and number is the value of the number. For a radix greater than 10, digits consist of the characters 0 through 9 and A through Z. In radix 16 (hexadecimal), for example, the digits are 0 through 9 and A through F. Table 11.13 shows the arithmetic operators supported by the Korn shell for use in arithmetic expressions. Table 11.13. Arithmetic operators in the Korn shell.
The Korn shell also supports expression grouping using parentheses. An expression in parentheses is evaluated as a unit before any terms outside the expression are evaluated. Parentheses are used to override the normal precedence of operators. The operators in Table 11.13 are listed in decreasing order of precedence. The Korn shell uses the normal precedence for arithmetic operators, which you know from the C programming language or from the use of an ordinary calculator. Because of these precedence rules, the expression a+b*y is computed first by multiplying b*y, and then by adding the product to a, just as though the expression had been written a+(b*y). With parentheses, you can change the order of calculation. For example, (a+b)*y would be computed first by adding a and b, and then by multiplying the sum by y. The let command is a built-in shell command. Like any command, it sets an exit value. The exit value of the let command is 0 if the value of the last or only expression computed is non-zero. If the last or only expression evaluates to 0, the exit value of the let command is 1. This strange inversion is an adaptation to the if statement, where a command setting a zero exit value is true--that is, it causes execution of the then clause--and a command setting a non-zero exit value is false--that is, it causes execution of the else clause. Because of the let command's inverted exit value, for example, the statement if let "a == b", when a and b are equal, is considered true. The logical result of the equality comparison would be 1, which is equivalent to if let 1. The last expression has a value of 1. Therefore, the exit value from let is 0, and the if statement is considered true, thus invoking the then clause as expected. Notice that you need to quote operators used in a let expression that are special to the shell. The command let prod=x|y would give very strange results if it were written without quotes. The shell would see a pipe between the two commands let prod=x and y. Acceptable quoting is any of the following forms:
Many Korn shell users employ the convention of always quoting an expression in its entirety, so they avoid the problem of shell metacharacters entirely. Take another look at the syntax of the let command. Notice that each of its terms is an arbitrary expression. A command such as let x+y is valid, but it is ordinarily of little use. This is because the sum of variables x and y is computed, but the result is thrown away. You should use an assignment expression--for example, let sum=x+y--to retain the result of the calculation in a variable named sum for later reference. The only time it makes sense to evaluate an expression without assigning the result to a new variable is when the purpose of the let command is to set a command exit value--namely, for use in statements such as if and while. In these cases, however, you can use a more convenient form of the let statement: the (( )) expression. A statement such as if (( x+y < 25 )) then ... fi is more clearly readable than this equivalent: if let "x+y < 25" An additional advantage is that using quotes to hide operators is unnecessary inside an (( )) expression. The (( and )) operators are in effect a special kind of parentheses. They notify the Korn shell that the text they enclose is intended to be an arithmetic expression; this turns off the normal interpretation of metacharacters such as < and |, and it permits the unambiguous interpretation of these symbols as operators. Compatibility with the Bourne shell isn't compromised, because the (( and )) operators don't occur in shell scripts written for the Bourne shell. You can use the (( )) expression form wherever the let command itself would be valid, as well as in a number of other places. Unlike the let command, however, the (( )) syntax permits only one expression between the doubled parentheses. There is also a version of (( )) that returns the string representation of the calculation; this is $(( )). In this form, the result is returned to the shell. For example, $ echo "(( 4+5 ))" (( 4+5 )) $ echo "$(( 4+5 ))" 9 You can use arithmetic expressions in any of these contexts:
Practical Examples of Arithmetic Now that you have reviewed all the basics of arithmetic in the Korn shell, you should take a look at some specific examples. This is an example of how not to use arithmetic expressions, for example: $ x=4 y=5 $ print x+y x+y The first command line assigns numeric values to the non-integer variables x and y. The print line attempts to print their sum, but the print command isn't one of the places where arithmetic expressions are supported. The result is fully compatible with the Bourne shell. The print statement simply echoes its arguments. Now look at a first attempt to fix the problem: $ let x=4 y=5 $ print $x+$y 4+5 The assignment statements have been changed to a let command, which has no significant effect on anything. The dollar signs ($) on the print statement help the shell recognize that x and y are variables. The variable references are substituted with their respective values, but the Korn shell still fails to recognize the presence of an expression on the print command argument. There is, in fact, no way to get the shell to recognize an expression and to evaluate it on a print command. Here is a working solution: $ integer x=4 y=5 $ let sum=x+y $ print $sum 9 The key element of the solution is the use of the let statement to calculate the sum. It stores the calculated result in a new variable called sum, which can be referenced later. You might think that using a hand calculator would be an easier way to perform a simple arithmetic problem at the keyboard, and I would tend to agree with you. At the keyboard, a more effective approach is simply to use the expr command. For example, $ expr 4 + 9 expr achieves the same result at the keyboard, but it is of little use inside shell scripts, where the result of the expr calculation--written to standard output--isn't readily available for use. Now consider this example of a counter-controlled loop: integer i=0 while (( i<5 )) do i=i+1 print $i done This little program simply prints the numbers 1 through 5. Notice the use of an assignment statement instead of a let command to increment i. This works only because the variable i was declared previously as an integer. The example works fine typed in at the keyboard. Try it. For a more practical example, consider the following: $ typeset -i16 hex $ hex=125 $ print $hex 16#7d Here, the variable hex is declared to be an integer and to be represented in base 16. The second line assigns a normal integer numeric value to the hex variable, and the third line prints it. Magically, though, the effect of the 16 from the typeset command becomes clear: The value of hex is shown in hexadecimal (base-16) notation. Going the other way--converting from hexadecimal to decimal--is just as easy: $ integer n $ n=16#7d $ print $((n)) 125 At the keyboard, after you declare the hex and n variables, they remain in effect indefinitely. You can use them repeatedly to convert between hexadecimal and decimal. For example, $ hex=4096; print $hex 16#1000 $ n=16#1000; print $((n)) 4096 Shell ProgrammingAlthough the main thrust of the Korn shell's features is to enhance productivity at the keyboard, the Korn shell also provides a number of boons for writing shell scripts, which makes the Korn shell an attractive environment for program development. This section reviews the Korn shell enhancements that apply to shell-script writing. Of course, all the programming constructs of the Bourne shell are available, so the material in Chapter 9 pertains equally to the Korn shell and isn't repeated here. The Korn shell extensions useful for writing shell scripts are conditional expressions, which enhance the flexibility of the following:
If you are going to be writing shell scripts that will be used by many people, it is wise to place this on the first line of the script: #!/bin/ksh This tells the user's shell under which shell the script actually should run under. Running a Korn shell script under the C shell, for example, just won't work no matter how hard you try! The section "Variables," earlier in this chapter, discussed the Korn shell's extended variable support, including array variables, integer variables, variable reference expressions, and arithmetic expressions. The other new features are explained in the following sections. Conditional ExpressionsThe 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.
FunctionsThe 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.
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 getoptsThe 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 StatementIf 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 CoprocessesThe 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:
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 TalesThe 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 ShellIt almost might be said that the term shell refers to what you have before you customize it--an empty shell. Of course, that's a gross exaggeration. The shell is more feature-laden than most programs you'll get an opportunity to shake a stick at. Still, the Korn shell permits so much customization that it's no exaggeration to say that you might find another user's logon environment so foreign as to be almost unusable by you. Indeed, some places try to place a limit on user customization. You can adapt the Korn shell to your preferred way of working in many ways. Of course, keep in mind that if you're a beginning UNIX user, you might not have many preferences. As your familiarity with UNIX and the Korn shell increases, you'll find many conveniences, shorthand methods, and customary uses that seem comfortable to you. The Korn shell helps you along by enabling you to encapsulate favorite behaviors into your logon profile script and elsewhere. Customizing the Korn shell begins with your logon profile script, which is named .profile and resides in your home directory. The file $HOME/.profile is of special importance, because the Korn shell executes it every time you log on--or, more precisely, every time you launch an interactive shell. Often, the system administrator will place a starter .profile script in your home directory when he creates your logon. Don't let yourself be cowed into thinking that there is anything sacrosanct in the hand-me-down .profile given to you. The contents of your .profile script affect only you. Your script is specific to your logon name and home directory. Altering it conceivably could affect only those people who have your password and can log on with your logon name. Almost always, that is only you. Therefore, you should feel free to add to, change, or delete anything in the .profile script, including deleting the whole file. It doesn't matter to the shell. The .profile is supported only for your convenience; it isn't needed for Korn shell operation. Your .profile script is, in fact, a shell script. Any shell-programming techniques valid in a shell script are valid in the .profile script. If you're not a shell programmer, don't be daunted. Useful logon profiles can be made up that contain nothing more than straightforward UNIX and shell commands, without an if or while statement in sight. If you know how to use shell conditional and iterative statements, so much the better. Don't think that mastery of them is essential to writing good profile scripts, though. It isn't. Your .profile script is an ideal place to put your favorite things. You might want to do the following things with your .profile file. You also should observe the order in which these items are listed. Placing similar things together helps simplify the job of maintaining your .profile.
Setting Control Keys with sttyUse 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 ulimitUsing 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.
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 ControlLocal 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 AliasesDefine 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 FunctionsDefine 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 OptionsIf 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 LogonExecute 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 ItWhenever 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 FileAfter 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.
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.
Adding Settings for Other Programs to Your .profileCustomizing 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 JobsThe idea of a job might be somewhat foreign to UNIX users, because in UNIX, most of the action is interactive. Nevertheless, even the Bourne shell provides basic tools for running background jobs, and UNIX the operating system always has provided such tools. The more recent releases of UNIX have even enhanced background job management. The basic idea of a background job is simple. It's a program that can run without prompts or other manual interaction and can run in parallel with other active processes. With the Bourne shell, you launch a background job with the & operator. The command cc myprog.c &, for example, compiles the source program myprog.c without tying up the terminal. You can do other work--even edit files with a full-screen editor--while the cc command works behind the scenes. Enhancements to the stty command and the terminal driver in recent UNIX releases have added a new control key to your terminal: Suspend. Suspend is usually Ctrl+Z. This new tool enables you to take an interactive program you're currently running, such as a vi editing session, and to put it temporarily into the background. If the program wants to talk to your terminal, the system suspends the program. Otherwise, it continues running. The Korn shell adds some tools that help you manage the family of processes you can accumulate. These tools consist of the jobs, kill, wait, bg, and fg commands. To use the Korn shell's job-control tools, you must have the monitor option enabled. Normally, the monitor option is enabled for you automatically; it's the default for interactive shells. If your operating system doesn't support job management, the default for the monitor option is off. Even without operating system support--the Suspend key and stty function are an operating system service, not a Korn shell service--you still can use some of the Korn shell's job-control tools, but you must set the monitor option on yourself. You do that with the command set -o monitor. The jobs command, which takes no arguments, simply lists the jobs that you currently have active. The output of jobs looks like this: $ jobs [1] + Running xlogo& [2] + Running xclock -bg LightGreen& [3] + Stopped vi myprog.c You use the kill, bg, and fg commands to manage jobs. When referring to a job, you use the job number shown in brackets in the output of jobs, preceded by a percent (%) sign. For example, kill %1 would terminate the xlogo program you currently have running. The wait, kill, bg, and fg commands also can refer to background jobs by their Process ID, which you generally can obtain from the output of the ps command. The use of Korn shell job numbers is preferred, however, because they are simpler and safer to use than Process IDs. You create jobs in one of three ways:
By convention, a job started or switched into the background continues to run until it tries to read from your terminal. Then it is suspended by the operating system until you intervene. When it is in this state, the jobs command shows that the command is Stopped. A job that has been stopped usually needs to talk to you before it can continue. In the previous jobs example, the vi command is shown to be stopped. The command won't continue until you reconnect it to your terminal. You do this with the fg command--for example, fg %3 or fg %vi. The vi command then becomes the foreground process, and it resumes normal interactive execution with you.
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.
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.)
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 & SummaryThis 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! |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|