|
|
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 thedocuemnt uses the operator << to tell the shell that all the text on the nextline to the label, in this case is !, is all to be read in and redirected tothe 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.
CAUTION: You'll notice that the expressions *(pattern[|pattern]...) and +(pattern[|pattern]...) will match any combination of the specified pattern. This can be both useful and dangerous. If in doubt, use echo to find out what files the patterns will match. You won't be popular if you end up removing the system configuration by mistake! Note that the definition of pattern expressions is recursive. Each form contains one or more pattern strings. This means that nested pattern expressions are legal. Consider, for example, time*(.[cho]|.sh). It contains the pattern [cho] inside the pattern expression, which causes it to match time.sh, time.c, time.h, time.o, time.sh.c, time.c.o, and so on. The pattern time*(.*(sh|obj)) matches the filename time.sh or time.obj. The main value of these extended pattern-matching expressions is in enabling you to select a subset of files without having to list each filename explicitly on the command line. Pattern expressions also are legal in other contexts where the shell does pattern matching, such as in the expression of the case statement. Command 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: $ aliastrue=letfalse=letlx=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. CAUTION: If you do get a handle on how the shell-quoting syntax works, it incites many otherwise nice people to brand you as a UNIX guru. So be careful. Using Exported 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.
CAUTION: Use caution when writing scripts that will use the privileged option. A badly written script may give potential attackers doors they need to access a more privileged user. In addition to the letter options listed in Table 11.4, the -o keyletter supports the additional named options listed in Table 11.5. Table 11.5. Options supported by the -o keyletter.
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:
NOTE: Performing command editing with the fc command, although a convenient and useful feature of command history, is not the same as the command-editing feature discussed later in the "Command Editing" section. Now take a closer look at these commands for viewing and manipulating command history. Displaying the Command-History 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
NOTE: The history command is actually an alias for the fc command--specifically, for fc -l. The complete syntax for the history command follows: history [first] [last]
For first, specify the first line to be displayed. You can designate a specific line directly by its line number--for example, history 35--or as a number of lines back from the current line--for example, history -10. You also can give the command name of the line from which the display should begin--for example, history vi. The Korn shell looks backward from the current line until it finds a command beginning with vi and then displays lines from that point forward. For last, specify the last line to be displayed. If you omit last, history lines are displayed from first up to the current, most recently entered line in the command history. You can use an actual line number, a relative line number, or a command name to designate the last line to be displayed. If you omit both first and last, the Korn shell lists the last 16 lines of history. TIP: You won't know what line numbers to use until you first list some history. Most people begin a search of command history without any operands. If you want to see more lines before line number 160, you might want to try history 140. Reexecuting a Command from the 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. NOTE: The r command is actually a preset alias for the fc command--specifically, fc -e -. The general syntax for r follows: r [ old=new ] [ line ]
If you omit line, the most recently entered command is reexecuted. Specify a line number (25), a relative line number (-8), or a command name (vi) for line to designate the command you want to reuse. As with the history command, if you specify a command name, the most recently entered command with that name is reused. You can modify a word or phrase of the reused command by using the syntax old=new. Suppose that the command history contains the following line: 135 find /usr -type f -name payroll -print
You could reuse the find command, changing only the filename payroll to vendors, like this: $ r payroll=vendors find
The r command echoes the line that will be executed, showing any changes that might have been made. For example, the r command here yields the following output: $ r payroll=vendors findfind /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 paywork136 mv paymast/newemps paywork137 cd paywork138 vi newemps139 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. CAUTION: Keystrokes you type while in Command mode are not displayed. You can see only the effect of an edit command--not the command itself. This can be unsettling if you're inexperienced with the vi style of editing or if you're entering a command of more than a few keystrokes. TIP: If you forget whether you're in Command or Edit mode, the invisible nature of Command mode can make your keyboard appear to go wild and not respond to your input in any recognizable fashion. If this happens to you, the best thing to do is to try to cancel the current line completely with the kill function--normally, by pressing the @ or Ctrl+U keys. If all else fails, press the Enter key. Pressing the Enter key might give you an error message when it attempts to execute a garbled command, but at least it is guaranteed to return you to Input mode. Table 11.7 summarizes the vi Edit mode commands. As you'll notice if you're already familiar with vi, nearly all the vi commands are supported--even those that cause movement upward and downward in a file. Commands that move from one line to another actually cause movement in the history file. This enables you to browse through the command history, select a command, modify it if necessary, and reenter it--all with a few simple keystrokes. Some commands can be prefixed by a count--a non-zero number. A count causes the command to be repeated that number of times. For example, B moves backward one word, but 12B moves backward 12 words. If you don't specify a count, it defaults to 1. A few commands--notably c (change), d (delete), and y (yank)--must be followed by a cursor-motion command. Such commands are marked with the right-arrow symbol ([ra]). Using cursor-motion commands is discussed after Table 11.7. Table 11.7. vi command editing: Command-mode commands.
NOTE: Although many vi editors support cursor keys for cursor control, the vi Edit mode does not recognize these keys, so you must use h, j, k, and l to control the cursor. The vi command-editing feature also supports a few control operations you can use while in Input mode, which are described in Table 11.8. Using one of these operations doesn't require you to switch to Command mode first, and it doesn't switch you to Command mode. Table 11.8. vi Command editing: Input mode commands.
Most vi commands can be preceded with a repeat factor, shown in the box as [n]. If you omit the repeat factor, the command executes its normal function one time. A repeat factor larger than 1 causes the command to repeat its action the specified number of times. Thus, 2W causes the cursor to skip forward not one but two words, and 7r. replaces seven characters, starting at the cursor position, with seven periods. Commands shown with the symbol [ra] require a cursor motion command following the main command letter. The c, d and y commands must be followed by a cursor motion command to define the amount of text to be changed, deleted or yanked (copied). The cursor motion command can be any command that, if by itself, would move the cursor beyond the desired text. For example, dw deletes the current word. cte changes text up to, but not including, the next e in the line. y0 yanks the characters from the beginning of the line up to, but not including, the character at the cursor position. Framing cursor-motion commands to meet your text-editing objectives is your responsibility. No prespecified limitations exist on the method for selecting a range of text; you are free to choose whatever comes naturally to you. Until you are comfortable with the use of cursor-motion commands, however, stick to simple combinations, such as cw or cW, to change a word. The capitalized cursor-movement commands B, E, and W differ from their lowercase counterparts in their choice of delimiters. The lowercase b, e, and w commands consider a word to end at the next nonalphanumeric punctuation character, which can be a blank or tab but also includes apostrophes, commas, and so on. The B, E, and W commands consider a word to be delimited strictly by blanks or tabs. They skip over, or select, punctuation characters as well as alphanumerics. Most of the commands leave you in Command mode. A few--a, A, c, C, i, I, R, and S--switch to Input mode to enable you to enter text. If, after entering the text, you are ready to execute the command, simply press Enter. If you want to edit the line some more, however, you must switch back to Command mode. In that case, press ESC after entering the desired text. Not all commands supported by the vi editor are shown in Table 11.8. Commands not shown are not supported by the built-in vi Edit mode of the Korn shell. Noteworthy omissions include the o and O (open) commands, the m (mark) command, and scrolling commands such as z, H, and M. These omissions are due to the difference between a command editor and a file editor. In a command-editing context, they have no useful purpose. NOTE: For a fuller discussion of the vi text-editing commands, refer to Chapter 3 in Volume II, "Text Editing with vi, and emacs." EMACS Edit 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. CAUTION: The EMACS Edit mode edits lines--not commands. Command history might contain multiline commands, such as if or while, if you use such commands at the keyboard. The vi Edit mode processes such commands as a single entity, but in EMACS Edit mode, you might need to use the Ctrl+O (operate) command to step through multiline commands when you retrieve them from the command history. The EMACS command-editing interface is an example of a user interface designed for an alien species, because it obviously requires the use of three hands to perform well. If you are a beginner or a casual user of command editing, you might nevertheless find EMACS Edit mode preferable to vi mode, because, with EMACS, there's no confusion between Command mode versus Input mode. As your proficiency and keyboard speed increase, however, the vi Edit mode becomes a more attractive interface. Table 11.9. EMACS Edit mode commands.
NOTE: The sequence Ctrl+? is not to be taken literally. It represents the ASCII Del (127) character. Most terminals generate the Del character in response to the Delete key, in which case ESC Delete is a synonym for ESC Backspace. NOTE: A macro is defined with the alias shell built-in command. Its name must begin with an underscore (_) and must be followed by one letter. The value of the alias is processed as if you typed the characters of the value at the time of invoking the macro. Thus, sequences such as Ctrl-f in the alias value move the cursor to its current position. The letter used in the macro name should not be b, c, d, f, h, l, or p; these letters already are assigned to EMACS commands. NOTE: In addition to using the control-key sequences to move the cursor, you can use the cursor-control keys to navigate the history list and move the cursor. The capability to do this depends on how your terminal is set up, though. NOTE: Changing character case also moves the cursor to the right, spacing over the changed character(s). NOTE: If the Ctrl-d key is assigned to the EOF function with the stty command, it is interpreted as your EOF key when typed at the beginning of the line. Otherwise, it performs the Delete function. NOTE: Most terminals generate Ctrl-h for the Backspace key. Some terminals generate the ASCII Del character (0177), though. Therefore, the shorthand ESC Backspace might not work for your terminal. NOTE: To use the operate (Ctrl-o) command, you must have previously established a position in the command-history file by using Ctrl-p, Ctrl-n, or another history command. Successive presses of Ctrl-o step through lines of command history in the forward--older to newer--direction, executing one line at a time. You have the opportunity to change each line before pressing Ctrl-o to execute it. NOTE: If set -o gmacs is used instead of set -o emacs, Ctrl-t transposes the current and preceding character, not the current and next. This is the only difference between EMACS and GMACS Edit modes. 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.
NOTE: I always put the following definition in my logon profile: CDPATH=.:..:$HOME The command cd src first looks for a directory named src as a subdirectory in the current directory. Failing that, the cd command looks for src in the parent directory. If no directory named src is found in either place, it tries to change to src in my home directory. I find that proper use of the CDPATH variable saves a lot of typing. As with the Bourne shell, variable names in the Korn shell begin with a letter or an underscore, and they contain an arbitrary number of letters, underscores, and digits. The variable name is a symbolic representation for the variable's value, which can be changed by an assignment statement; by the set, read, or select statement; as a by-product of the execution of a built-in shell or other commands; or by the Korn shell itself. There is no arbitrary upper limit to the number of variables you can define and use, but the amount of memory available to the shell sets a practical (usually large) upper limit. You can explicitly assign a value to a variable name by using an assignment in the format name=value. Note that you don't include a dollar sign ($) in front of name when you write the assignment. The dollar sign is appropriate only when referring to the value of the variable. The value of a variable is a string--a sequence of alphanumeric and special characters--of arbitrary length. The Korn shell provides a number of extensions that enable the value of a variable to be manipulated by arithmetic methods. The variable's value still is stored as a string, however. A variable retains its value from the time it is set--whether explicitly by you or implicitly by the Korn shell--until the value is changed or the shell exits. Note that the value isn't passed to commands and shell scripts that you invoke unless the variable is marked for exportation. You mark a variable for exporting with the typeset built-in shell command or the export alias. Alternatively, if the allexport option is switched on (by typing set -o allexport, for example), all variables created are exported automatically. Exported variables become part of the environment of all invoked commands. Because the values of variables are retained internally in a memory table by the shell, all variables that the shell didn't inherit are lost when the shell exits. For this reason, you cannot assign a value to a shell variable inside a shell script--one invocation of the shell--and expect the value to be retained after the shell script exits; the shell returns to a higher level shell. In other words, you can assign values to variables and export the variables to pass values downward to subshells of your current shell, but you cannot pass values upward to higher level shells or shell scripts. This limitation on the use of shell variables isn't normally visible to you at the keyboard. It generally arises in issues related to shell programming. However, if you invoke the shell directly (by entering the sh, ksh, or csh command) or indirectly (by entering the shell environment from within another UNIX command, such as vi or pg), you should realize that any changes to the shell environment, including variable settings and aliases, are lost when you return to your original shell level by exiting the subshell. Referencing 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 $Person2John and Mike$ print $Person1and$Person2Person1and: not defined$ print ${Person1}and$Person2JohnandMike
{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 $wordshellodshellodshellodshello{{{{e value }of the expression is the value of all the elements of the array variable name that are set, separated by blanks. Substitution occurs in the same way as for the special expression $* with regard to embedded blanks and word splitting. For example, $ set -A planets Mercury Venus Earth Mars$ planet[9]=Pluto$ print ${planets[*]}Mercury Venus Earth Mars Pluto {name[@]} The value of} the expression is the value
of all the elements of the array variable name that are set,
separated by
blanks. If elements of the array contain strings with embedded blanks and if
the expression ${name[@]} is
contained inside quotes, the number of words in the
substituted expression is equal to the number of non-null array elements.
Otherwise, embedded
blanks cause word splitting to occur, and the number of substituted words will
be greater than the number of non-null array elements. For example, $ set -A committee "B Jones" "M Hartly" "C Rogers"$ for word in ${committee[@]}> do> print $word> doneBJonesMHartlyCRogers$ for word in "${committee[@]}"> do> print $word> doneB JonesM HartlyC 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 $FNAMEs.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 $FNAMEs.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 $FNAMEs.myfile.c$ print ${#FNAME}10 {#name[*]} The value of the expression is the number of elements
of the array variable name that are set. For example, $ set -A planets Mercury Venus Earth Mars$ print ${#planets[*]}4 {#name[@]} {#name[@]} is the same as {#name[*]}. Array Variables
An array variable is a variable with more than
one value. Array variables are
helpful for managing lists of strings, because you can reference an
individual element
in the list without resorting to string-splitting techniques. You can assign values to an array one at a time
by using the assignment statement. For
example, $ planets[1]=Mercury$ planets[2]=Venus$ planets[3]=Earth$ print ${planets[2]}Venus The general syntax name[subscript] is
supported by
the Korn shell for referring to elements of an array. For subscript, supply
an integer number in the range of 0 through 511, or write a variable
expression with
the value of the desired element number. Element numbers begin at zero. Thus, the
first element in an array is ${name[0]}. You can use the -A option of the set
command to set many array elements with one statement. For
example, the preceding code could be rewritten as this: $ set -A planets Mercury Venus Earth$ print ${planets[2]}Venus You also can substitute all the elements of an
array by using the special notation ${name[*]}
or ${name[@]}. For example, $ set -A planets Mercury Venus Earth$ planets[9]=Pluto$ planets[7]=Uranus$ print The known planets are: ${planets[*]}The known planets are: Mercury Venus Earth Uranus Pluto You should remember a few points when using
array variables: · If you reference the array variable without a subscript, the value of the reference · is the first element of the array: · $ print $planets Mercury · Array variables cannot be exported. · ·
· The special expression ${#name[*]} or ${#name[@]} · can be used to get the number of non-null elements in an array. For example, · $ print There are ${#planets[*]} planets: ${planets[*]} There are 5 planets: Mercury Venus Earth Uranus Pluto · You must use the brace-enclosed expression syntax to refer to elements of an · array. Without the braces, the Korn shell interprets the expression in the same way · the Bourne shell would. For example, · $ print The known planets are $planets[*] The known planets are Mercury[*] $ print The second planet from the Sun is $planets[2] The second planet from the sun is Mercury[2] Variable Arithmetic
An exciting new addition to the capabilities of
the old Bourne shell offered by the
Korn shell is the capability to do arithmetic. The Bourne shell provides no
built-in calculating
capability, so even the simplest arithmetic requires command substitutions that resort to calling other UNIX
programs such as expr. The Korn shell adds
some built-in capabilities to do basic arithmetic. The two major tools you'll use when doing
arithmetic inside the Korn shell are the
typeset command and the let command. The typeset
command provides
number-formatting capability and the capability to declare--or set
aside--some variables
for the special purpose of doing arithmetic. The let command is where
all this magic really happens. Using typeset The Korn shell is still a very slow tool for
performing repetitive
calculations, even with the typeset statement. Floating-point--real numbers
with decimal points, fractions, and so on--calculations aren't supported. Therefore,
all your calculations must use integer values, and they will yield integer results.
The shell arithmetic is sufficient to support programming concepts such as
loop control with counters, however. The typeset statement is an extension
provided by the Korn shell to permit some
amount of control over the format and use of shell variables. When typeset is
used for managing variables, its syntax follows: typeset [ +/-HLRZilrtux [n] ] [ name[=value] ] ... The particular set of options you use with the
command determines the required format
for the syntax of the command. Not all combinations of option letters are legal.
Only the options listed in Table 11.12 should be specified. Table 11.12. typeset options.
Apart from exporting variables, usually by way
of the export alias, the typeset
command is used mainly for two purposes: · Setting up variables that you plan to use for calculation as integer variables · ·
· Defining special formatting options for variables ·
Although the Korn shell doesn't require that a
variable be declared as an integer to
do arithmetic with it, doing so provides some advantages. Calculations are
more efficient
when you use arithmetic variables in the let statement, because the
shell can maintain the numeric value of the variable in an internal binary
format, which
is more suitable to the computer's math instructions. Similarly, there are contexts
in which the shell recognizes arithmetic operators in an expression if the expression
contains integer variables, but it won't if the expression uses standard variables. The general procedure for using typeset
to define integer variables is straightforward.
Before using variables for calculation, simply issue a typeset command
to declare the variables as integers. For example, typeset -i x y sumread x ylet sum=x+yprint $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 sumread x ylet sum=x+yprint $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=0while ...do let suffix=suffix+1 print sampfile.$suffixdone 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: · let "prod=x|y" · ·
· let prod="x|y" · ·
· let prod=x\|y ·
Many Korn shell users employ the convention of
always quoting an expression in its
entirety, so they avoid the problem of shell metacharacters entirely. Take another look at the syntax of the let
command. Notice that each of
its terms is an arbitrary expression. A command such as let x+y is
valid, but
it is ordinarily of little use. This is because the sum of variables x and
y is computed, but the result is thrown away. You should use an
assignment expression--for
example, let sum=x+y--to retain the result of the calculation in
a variable named sum for later reference. The only time it makes
sense to
evaluate an expression without assigning the result to a new variable is when the
purpose of the let command is to set a command exit value--namely,
for use
in statements such as if and while. In these cases,
however, you
can use a more convenient form of the let statement: the (( )) expression. A statement such as if (( x+y < 25 ))then ...fi is more clearly readable than this equivalent: if let "x+y < 25" An additional advantage is that using quotes to
hide operators is unnecessary inside
an (( )) expression. The (( and )) operators are in
effect a special kind of parentheses. They notify the Korn shell that the
text they
enclose is intended to be an arithmetic expression; this turns off the normal interpretation
of metacharacters such as < and |, and it permits the
unambiguous interpretation of these symbols as operators. Compatibility with the
Bourne shell isn't compromised, because the (( and ))
operators don't
occur in shell scripts written for the Bourne shell. You can use the (( )) expression form
wherever the let command itself
would be valid, as well as in a number of other places. Unlike the let command,
however, the (( )) syntax permits only one expression between the doubled
parentheses. There is also a version of (( )) that
returns the string representation of
the calculation; this is $(( )). In this form, the result is
returned to
the shell. For example, $ echo "(( 4+5 ))"(( 4+5 ))$ echo "$(( 4+5 ))"9 You can use arithmetic expressions in any of
these contexts: · As an array subscript · ·
· As arguments of the let command · ·
· Inside doubled parentheses (( )) · ·
· As the shift count in shift · ·
· As operands of the -eq, -ne, -gt, -lt, -ge, · and -le operators in test, [, and [[ commands · ·
· As resource limits in ulimit · ·
· As the right-hand side of an assignment statement, but only when the variable · name being assigned was defined as an integer variable with the typeset · or integer statement ·
Practical Examples of Arithmetic Now that you have reviewed all the basics of
arithmetic in the Korn shell, you should take a look at some specific
examples. This
is an example of how not to use arithmetic expressions, for example: $ x=4 y=5$ print x+yx+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+$y4+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 $sum9 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=0while (( i<5 ))do i=i+1 print $idone 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 $hex16#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 $hex16#1000$ n=16#1000; print $((n))4096 Shell Programming
Although the main thrust of the Korn shell's
features is to enhance productivity at
the keyboard, the Korn shell also provides a number of boons for writing
shell scripts,
which makes the Korn shell an attractive environment for program development. This
section reviews the Korn shell enhancements that apply to shell-script
writing. Of
course, all the programming constructs of the Bourne shell are available, so
the material
in Chapter 9 pertains equally to the Korn shell and isn't repeated here. The Korn shell extensions useful for writing
shell scripts are conditional expressions, which
enhance the flexibility of the following: · if, while, and until statements · ·
· Array variables, integer variables, extended variable reference expressions, · and arithmetic expressions · ·
· A new select statement for constructing a menu of prompts from which · the user can select a choice · ·
· Extended support for functions, including autoload functions · ·
· An enhanced form of the command expression $(...), which is simpler · to use than the backquoted form ´...´ · ·
· Extended support for process communication withcoprocessing using the operator--|&. ·
If you are going to be writing shell scripts
that will be used by many people, it
is wise to place this on the first line of the script: #!/bin/ksh This tells the user's shell under which shell
the script actually should run under. Running
a Korn shell script under the C shell, for example, just won't work no matter how
hard you try! The section "Variables," earlier in
this chapter, discussed the Korn shell's
extended variable support, including array variables, integer variables, variable
reference expressions, and arithmetic expressions. The other new features are
explained in the following sections. Conditional Expressions
The if, while, and until
statements support two new kinds
of expressions. The (( )) doubled parentheses operator, which
evaluates an
arithmetic expression, enables you to perform complex arithmetic tests. A
zero result
is considered true, and a non-zero result is considered false. You also can write
an extended conditional test expression as the argument of if, while, or
until. A conditional test expression has this general form: [[ conditional-exp ]] where conditional-exp is any of the
forms shown in Table 11.14. Notice that the conditional-expression forms are
similar to those of the test or
[ ] expression. The Korn shell supports the test and [
] expressions
identically with how the Bourne shell does. The [[ ]] expression provides
extended capabilities without compromising compatibility with the Bourne shell. Table 11.14. Conditional expressions.
Functions
The Korn shell fully supports Bourne shell
functions. It also provides some extensions. Defining Functions In addition to the Bourne shell syntax, the
Korn shell supports
the following alternative syntax for defining a function: function identifier{ command-list} Using Variables in Functions The Korn shell allows a function to have local variables.
A local variable exists only during the execution of the function and
is destroyed when the function returns. A local variable can have the same
name as
a variable in the calling environment. During execution of the function, the
local variable
hides the outer variable. You define a local variable with the typeset command.
For example, function square{ typeset product let "product=$1*$1" print $product return} Using Traps in Functions In the Bourne shell, traps set with the trap command
remain in force after the function's return. In the Korn shell, traps set in
the calling environment are saved and restored. You can use the typeset command with
the -f option to manage functions.
The -f option has four forms, which are listed in Table 11.15. Table 11.15. -f option forms.
Using Autoload Functions Autoload functions provide superior performance versus
conventional shell scripts, because they are retained in memory for fast
execution on
repeated calls; however, unreferenced functions incur no overhead other than
processing of
the typeset -fu command. You create autoload functions in much the
same manner
as shell scripts, except that the definition file should be in the form of a
function; it should begin with the statement function name. To use
autoload functions, you must set the FPATH environment variable to
the directory
or directories to be searched (in the same manner as you set the PATH environment
variable), and you must declare the functions in advance with the typeset -fu command. Any function definition is eligible for use as
an autoload function, although frequently
used functions are preferred. Remember that after an autoload function is
read, its definition is retained in the shell's available memory. Large
programs should
be written as conventional shell scripts instead of as autoload functions unless
the program is used heavily. Undefining Functions To undefine a function, use the unset
command: unset -f name ... The named functions are purged from memory, and
any typeset -fu declaration for
the named function is deleted. The unset -f command is not used
often, but
it is useful particularly when debugging a function. Using unset -f is
the only way to force the shell to reread an autoload function definition
file. When To Use Functions Functions are a handy way of creating new
keyboard commands.
Because a function executes as part of the current shell environment, a directory
change made with the cd command remains in force after the function exits.
This isn't true for ordinary commands and shell scripts. Because I almost always
like to take a quick peek at a directory's contents after changing to it, I
created the following short function definition and added it to my logon
profile: function go{ cd $1 /usr/bin/ls -FC} The go function, used in the form go
dirname, not only changes
to the directory but also prints a sorted listing so that I can see
immediately what's
in the directory. Adding the go function to my logon
profile means that it's always present in
the shell memory. Because go is a small function, this does no harm, considering
how often I use it. For larger functions, it is better to store the function definition
in a separate file and to replace the function definition in the profile with
a typeset -fu declaration, thus making the function an autoload function. Scanning Arguments with getopts
The Bourne shell provides negligible assistance
with the processing of command-line options.
As a result, many user-written shell scripts process options clumsily at best,
and they often don't support the generalized UNIX command format for options. The
getopt command, long a standard part of the UNIX command set, helps a
little. The Korn shell, however, goes one step further by adding a built-in
command called
getopts, which provides the same power and flexibility to script writers
that C programmers have long enjoyed. The syntax of the getopts built-in
command is straightforward: getopts options var [ arg ... ] For options, provide a string that
defines the letters that can legally
appear as command-line options. If an option letter can be followed by a value
string, indicate this in the options string by following the letter
with :. For example, I: represents the option syntax -Istring. If options begins with :, the
Korn shell provides user error
handling. The invalid option letter is placed in OPTARG, and var is
set to ?. Without :, the getopts command issues an error
message on an invalid letter and sets var to ? so that
you can recognize that an error occurred and skip the invalid option, but it doesn't
identify the invalid letter. For var, write the name of a variable
to receive the option letter. The
shell stores the letter in var when it identifies the letter as
an option in the command line. For arg, write the argument list from
the command line that is to
be scanned for options. The arg list usually is written in the form
$* or "$@". For reasons of practicality, the getopts
command cannot scan, identify, and
process all option letters in a command on one invocation. Instead, each time you
call getopts, you get the next option on the command line. Of
course, getopts
can't look at the real command line that invoked your shell script. It
examines the arg list that you provide with getopts, stepping
once through the list on each call. When you call getopts, it starts by
determining its current position in
the arg list. If its current position is within a word and the word
starts with -, the next character in the word is taken as an option letter.
If this is your first call to getopts, or the last invocation
finished scanning
a word, getopts examines the next arg for a leading hyphen. In any case, when getopts identifies an
option, it stores the letter in
var. If the option takes a value string (indicated in the option string
by being followed by :), the option value is scanned and stored in a
predefined variable named OPTARG. If getopts has started a
new arg
variable, it increments the predefined variable OPTIND to
indicate which argument it is working on--1, 2, and so on. It then updates
its position
in the argument list and exits. After calling getopts, you inspect the var
variable to find
out which option has been identified. If the option takes a value, you'll
find its
value string in the predefined variable OPTARG. The return value
from getopts
is zero if it finds an option, or non-zero if it can find no more options
in the command-line argument list. The code for using getopts is almost a
set piece that you need to memorize. Listing
11.1 is a shell program for scanning command-line options like those you might
find in a script file. Here, the example merely prints the options it
recognizes. Listing 11.1. Scanning options with getopts.
# A routine to scan options# ... allowable options are -a, -c, -R, -Aname, or -Iname. while getopts :acRA:I: KEY $*do case $KEY in a) print Found option -a;; c) print Found option -c ;; R) print Found option -R ;; A) print Found option -A, value is "'$OPTARG'" ;; I) print Found option -I, value is "'$OPTARG'" ;; *) print -u2 Illegal option: -$OPTARG esacdone# Strip option arguments, leaving positional argsshift OPTIND-1print ARGS: $* The code in Listing 11.1 is executable. Enter
the statements into a file and mark the
file executable with chmod +x filename. Then invoke the file's name
with a sample set of option letters and arguments. You'll see the shell
script's idea
of the options and positional arguments that you entered. You should note two special points about Listing
11.1. First, the option string
for the getopts command begins with a colon (:). When the option
string begins with a colon, the getopts command provides user
error handling; an unrecognized option letter is put into the OPTARG variable,
and the var keyletter variable is set to ?. You can
test explicitly for ? as the letter value, or you simply can provide your
own error message for any unrecognized option letter. If the option string doesn't begin with
:, getopts provides
its own error handling. After finding an unrecognized option letter, getopts prints
an error message and sets var to ?, but it doesn't set
the option letter in OPTARG. Therefore, although you can tell that
an invalid
option has been found, you don't know what the invalid letter is. Of course, an
invalid option letter is simply any letter that doesn't appear in the option string. Second, note the use of the shift
statement to identify the remaining position
arguments from the original command line. By itself, the getopts command
doesn't strip words containing options from the arg list. After
identifying options with getopts, however, you don't want to see
them again
when you examine the remaining positional arguments. You must throw away the option
words yourself. The shift statement, inherited from the Bourne
shell, does
the job eminently well, assisted by the arithmetic expression-handling syntax of
the Korn shell. The expression OPTIND-1 computes the number of
positional arguments
remaining on the command line. Notice that, because OPTIND-1 occurs in
the shift command line in the position of an expression, OPTIND is
recognized as a variable reference; you don't need to include a dollar sign
in front
of it. Using the select Statement
If you've ever written a shell script that
enables the user to specify values on
the command line or to be prompted for them, you know what an elaborate piece of
drudgery such a user-interface nicety can be. The Korn shell helps you out,
though, with
a new built-in command that automates the entire process--from printing a
selection menu
to prompting for the user's choice to reading it. In fact, because the user might choose an
illegal option (requiring you to repeat the
menu-selection process) or in case you want to display the menu repeatedly
until the
user decides to quit, the select statement is actually an iterative statement,
much like while or until. You must use the break statement
to terminate execution of select. The syntax of the select statement
follows: select identifier [ in word ... ]do command-listdone 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." ;; esacdoneprint "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)." esacdone Using Coprocesses
The Bourne shell supports a minimal amount of
communication between processes--typically, by
way of the pipe operator. You can invoke the ed line editor from a
shell script
to make a specific text change by using a command such as the one shown in Listing
11.3. Listing 11.3. Basic Process Communication.
(echo "/^Payroll+1i"cat newlistecho "."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>&pexec 4<&pread -u4 # discard initial message lineprint -u3 P # Turn on promptingprint -u3 "/^Payroll" # search for the insert locationread -u3 # read prompt indicating success or failurecase "$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 ;; esacdone You should note the following in this example: · The exec command (exec 3>&p) is used to move the coprocess · input pipe from its default location to a numbered file descriptor. · ·
· The exec command (exec 4<&p) is used again to move the · coprocess output pipe to number file descriptor 4. · ·
· Subsequent read and print commands specify the file descriptor · as the source or destination of the operation, using the -u option. · ·
· Ordinary UNIX commands can write to the coprocess by redirecting to file descriptor · 3 (cat filename >&3). ·
NOTE: Use read -p or print -p to read from or write to the coprocess until you have moved the coprocess input or output to a number file descriptor. Then read or write to that file descriptor: read-u4 or print -u3.
Admittedly, program 11.4, which uses
coprocessing, is more complicated than program 11.3,
but it is also safer. The Bourne shell version would have added new lines
after the
first line if the search for Payroll failed. The Korn shell version fails
gracefully without damaging the paylist file. Notice that the Korn shell example of
coprocessing in Listing 11.4 contains an incomplete
cat command. This is because you need a special syntax to transcribe a
file into the coprocess pipe. The standard Bourne shell syntax-->filename and
>&fildes--is inadequate. This is because >filename and
>&fildes do not give you a way to reference the coprocess input and
output pipes. Actually, by using a Korn shell feature designed
especially to support coprocessing, you
can use I/O redirection to send output to or read input from the background
process with
any UNIX command. The technique required is to switch the default input and output
pipes created by the |& operator to explicit file descriptors. You
use the exec command to do this: exec 3>&p When used with the exec command, this
special form of output redirection operator
causes the pipe for writing to the coprocess to be assigned to file
descriptor 3.
(The lack of a command on the exec statement, of course, tips off
the Korn
shell that you want to modify the current environment instead of execute
another program.) Similarly, the following code reassigns the pipe
for reading from the coprocess: exec 4<&p If you place these two lines at the front of the
ed example, the cat command
can be written in the familiar fashion--by using I/O redirection to an open file
descriptor. For example, cat newlist >&3 Of course, the new syntax for the exec
statement is a terrible kludge, amounting
to a form of syntactic code that is difficult to remember. However, the basic
outlines of coprocessing, including the |& operator and the -p options
for print and read, are straightforward enough, as is the underlying
concept. Coprocessing is a powerful capability, making it possible to do
things in a shell script that previously required the C programming language. So
sharpen up your coding pencils and try your hand at coprocessing. Cautionary Tales
The Korn shell is a very powerful shell to
script with; however, it has its problems. One
of the more obscure problems involves piping. Consider this script: person=nooneecho At start: $personwho | while read person tty junkdo echo $person is logged on at terminal $ttydoneecho At end: $person What will be the value of person after you run
this script? The answer is you don't
know--you can't know. This script gave me two different results on two
different implementations
of the Korn shell. On one system, person was an empty (null) string.
On the other system, it contained noone. The reason for this unpredictability is that
you're piping the output into another command.
When you use a pipe, you effectively start another shell to manage the
output. Different
implementations may carry out the piping in a different way, though, because while
and read are internal to the shell, so there is no need to start
a second shell to manage them. Don't write a scripts that work under one
implementation of a shell perfectly. Little
bugs like this can creep in and render your script unusable. Create
safeguards against
this by saving variables and restoring them. One day, your script actually might
be needed on a different system, and the last thing you want is lots of
people asking
you why it won't work. Customizing the Korn Shell
It almost might be said that the term shell
refers to what you have before you
customize it--an empty shell. Of course, that's a gross exaggeration. The
shell is
more feature-laden than most programs you'll get an opportunity to shake a
stick at.
Still, the Korn shell permits so much customization that it's no exaggeration to
say that you might find another user's logon environment so foreign as to be
almost unusable
by you. Indeed, some places try to place a limit on user customization. You can adapt the Korn shell to your preferred
way of working in many ways. Of course,
keep in mind that if you're a beginning UNIX user, you might not have many preferences.
As your familiarity with UNIX and the Korn shell increases, you'll find many
conveniences, shorthand methods, and customary uses that seem comfortable to you.
The Korn shell helps you along by enabling you to encapsulate favorite
behaviors into
your logon profile script and elsewhere. Customizing the Korn shell begins with your logon
profile script, which is named .profile
and resides in your home directory. The file $HOME/.profile is
of special importance, because the Korn shell executes it every time you log
on--or, more
precisely, every time you launch an interactive shell. Often, the system administrator will place a
starter .profile script in
your home directory when he creates your logon. Don't let yourself be cowed
into thinking
that there is anything sacrosanct in the hand-me-down .profile given
to you. The contents of your .profile script affect only you. Your script
is specific to your logon name and home directory. Altering it conceivably could
affect only those people who have your password and can log on with your
logon name.
Almost always, that is only you. Therefore, you should feel free to add to, change,
or delete anything in the .profile script, including deleting the whole
file. It doesn't matter to the shell. The .profile is supported only for
your convenience; it isn't needed for Korn shell operation. Your .profile script is, in fact, a
shell script. Any shell-programming techniques
valid in a shell script are valid in the .profile script. If you're
not a shell programmer, don't be daunted. Useful logon profiles can be made up
that contain nothing more than straightforward UNIX and shell commands,
without an
if or while statement in sight. If you know how to use
shell conditional
and iterative statements, so much the better. Don't think that mastery of
them is essential to writing good profile scripts, though. It isn't. Your .profile script is an ideal place
to put your favorite things. You might
want to do the following things with your .profile file. You also should
observe the order in which these items are listed. Placing similar things together
helps simplify the job of maintaining your .profile. · Set control keys with the stty command. · · Set environment variables. · · Set local variables for shell control. · · Define aliases you like to use. · · Define functions you like to use, including autoload functions. · · Set your favorite shell options. · · Execute commands you want to run each time you log on. ·
Setting Control Keys with stty
Use the stty command to establish the
control keys that you prefer to use.
The default Erase key is #, and the default Kill key is @. Both
are bad choices, because their use as terminal control characters conflicts with
their use as ordinary text characters. You should redefine these keys with a statement
similar to this: stty erase '^H' kill '^U' intr '^C' This example uses the caret (^) in
front of an upper- or lowercase letter to
designate a control-key combination. Thus, erase '^H' specifies the
Ctrl-h key
combination as your Backspace key. Of course, you would prefer to specify the actual
characters generated by your Backspace key as the value for the erase character--if
you can figure out what it is. The presence of a caret forces the use of quote marks. The caret is special
to the shell; a lack of quotes causes improper interpretation
of the stty command. (For details about the stty command,
see your UNIX User's Reference Manual.) Controlling Resources with ulimit
Using ulimit to control resources can
be a handy feature, especially if
you are a system administrator. Although UNIX comes with a ulimit
command, the
Korn shell offers its own alternative. The syntax for ulimit
follows: ulimit [-HSacdfnstv] [limit] The H and S flags tell ulimit
that you are defining a
hard or soft limit. A hard limit cannot be increased after it is set. A soft
limit can be modified up to the value of the hard limit. If both H and
S are omitted, the specified limit is applied to both the hard and
soft limits. If limit is omitted, the current value
of the specified limit is displayed. If
ulimit is invoked with no options, it returns the number of blocks
that can
be written by a process (the same as typing ulimit -f). Table 11.16 lists
the ulimit parameters. Table 11.16. The ulimit parameters.
TIP: Unless you are going to be doing a lot of programming, it is useful to place ulimit -c 0 in your profile. This prevents any program that crashes from creating a core file, so it also saves disk space. Many core files can be megabytes in size, so any way of reducing them is often a welcome method!
Setting Environment Variables At the very least, you'll want to make sure that
the variables PATH and MAIL have values. Usually, you'll
want to
set a great many more variables. If you use Bourne shell syntax, your
variable settings
will look like this: PATH=/usr/bin:/usr/ucb:/usr/local/bin:$HOME/bin:MAIL=/var/spool/mail/$LOGNAMEMAILCHECK=60FCEDIT=/usr/bin/viVISUAL=/usr/bin/viexport 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/$LOGNAMEexport MAILCHECK=60export FCEDIT=/usr/bin/viexport VISUAL=/usr/bin/vi When you write your environment variable
settings, keep in mind that some are set
by the UNIX logon processor. Your system administrator also can provide a
logon script
to set values before your .profile script runs. The PATH and
MAIL variables usually have initial values already set when your
script starts,
for example. Overriding the default PATH variable is usually a good idea;
you should have full control over your program search path, starting with its initial
value. Overriding the default MAIL or MAILPATH variable is
risky unless you know which mail subsystems are in use. Setting Local Variables for Shell Control
Local variables are variables the shell uses but
aren't exported. They include FCEDIT,
which designates the text editor to be used by the fc command, and
the PS1 variable, which is your primary prompt string. You also
might want
to define a few local variables to hold the names of directories that you
commonly access,
which enables you to use cd $dir instead of the longer full
pathname. Defining Aliases
Define the aliases you like to use. You must
invent your own aliases; each user tends
to have a different set. Most users make up some aliases for the ls command.
You even can redefine the default behavior of the ls command by defining
an alias named ls. Here are some typical aliases I like to use: alias lx='/usr/bin/ls -FC'alias l='/usr/bin/ls -l'alias pg='/usr/bin/pg -cns -p"Page %d:"'alias mail='/usr/bin/mailx'alias -t vi Notice that, in most cases, I tend to use the
full pathname for commands in the alias
definition. I do this because it eliminates directory searches for the
command, and
it provides much the same effect as the Korn shell's alias-tracking
mechanism. Note
also the explicit use of the alias -t command to request the shell to
track the vi command. The shell looks up the full pathname of the vi command
and defines an alias named vi for me so that the plain command vi has
all the performance but none of the typing overhead of /usr/bin/vi. Defining Functions
Define any functions you like to use, including
autoload functions. I use some function
definitions as keyboard shorthand, because a function can do things an alias can't.
You might want to use the go function described earlier in this
chapter, for
example, for switching directories. Setting Shell Options
If you find yourself frequently setting the same
shell options at the command line,
you can set them in your .profile instead. To set the preferred
shell options,
use the set command. If you prefer to use vi mode for command
history and
editing, and you want full job control support, you might add these two lines to
your .profile: set -o viset -o monitor Executing Commands Every Time You Logon
Execute commands you like to run every time you
logon. You might want to run the who
command to find out who's currently logged on, for example. Similarly, df,
which isn't present on all UNIX systems, displays the amount of free disk
space available on mounted file systems. Executing Your .profile After Changing It
Whenever you change your .profile
script, you should execute it before you
log out. If you make an error in your script, you might have difficulty
logging back
on. To test your .profile script, you can run it with the . (dot)
command: $ . ./.profile Be sure to leave a space after the first period:
it's the command name, and ./.profile is
the command argument. (Although .profile usually is adequate by
itself, you
might need to use ./.profile if your current directory is not in the search
path.) The dot command not only executes the script but also leaves any
environment changes
in effect after the script terminates. Alternatively, you can run the script with ksh
-v to have the shell execute the
script and print each statement as it is executed: $ ksh -v ./.profile Using the -n option would cause the
Korn shell to read your .profile and
check it for syntax errors but not execute the commands it contains. Creating an ENV File
After you have your .profile set up the
way you want, you're ready to tackle
the environment file. The environment file is any file that contains shell
scripts you designate by assigning its pathname to the ENV variable. The
shell executes the ENV file whenever you start a new invocation of
the shell
and when it executes a command. If you've ever shelled out from commands like pg
and vi, you know that when you call the shell again, some
environment settings,
such as aliases, aren't carried over from your logon shell. By placing aliases,
function definitions, and even global variable settings in a separate file and
setting ENV to its pathname in your .profile script, you
can ensure
that you have a consistent Korn shell environment at all times. Don't get carried away, though. In some cases,
the file designated by the pathname value
of ENV is executed in front of shell commands that you call. Because many
UNIX commands are implemented as shell scripts, this means that a large
environment file
can add surprising overhead to some unexpected places.
NOTE: As a rule, the environment file is executed as a preliminary step to invoking a shell script only when the shell script requires a new invocation of the Korn shell. This usually isn't the case when you invoke a shell script by its name.
To use an environment file, create a file that
contains the aliases, functions, and
exported variable settings you prefer. Then add the statement export
ENV=pathname, where
pathname is the full pathname of your environment file, to your
.profile. The environment file becomes effective the next time you log
on. It becomes effective immediately if you test your .profile with the
following . command: . .profile Commands you want to put in your ENV
file include alias definitions and shell
options. You may prefer them in here instead of .profile to be sure of
always getting a shell that looks and acts the same way each time.
TIP: A very useful if statement to put in your ENV file follows: if [[ -o interactive ]]then .... insert your ENV lines in here. .... fi
Any lines placed inside the if
statement are executed only if the shell is
to be interactive--that is, it gives you a prompt at which you can type
commands. This
can cut down on the overhead of processing a new shell many times if the
shell is
being called with a command line that will run a command--for example, ksh -c ls -l
If you have a lot of aliases and/or functions,
it might be a good idea to place these
in a separate file again and call this file from ENV to set them up. In
my ENV file, I have these two lines: . .ksh_alias. .ksh_funcs
In .ksh_alias, I've placed all my alias
definitions, and in .ksh_funcs, I've
placed all my function definitions. This shortens my ENV file
substantially and
makes everything look a lot neater.
Adding Settings for Other Programs to Your .profile
Customizing your environment doesn't stop with
using the logon profile and environment file
to establish shell options and settings you want; it's also a handy place to put
settings used by other programs. One way to customize your vi editing
environment is
by defining a variable EXINIT that contains the commands vi will run every
time you start it. You could place the EXINIT variable setting in your
logon profile to establish your preferred vi settings. Many UNIX commands
respond to
environment variables, which enables you to customize these commands in your
logon profile. Controlling Jobs
The idea of a job might be somewhat foreign to
UNIX users, because in UNIX, most of
the action is interactive. Nevertheless, even the Bourne shell provides basic tools
for running background jobs, and UNIX the operating system always has
provided such
tools. The more recent releases of UNIX have even enhanced background job
management. The basic idea of a background job is simple.
It's a program that can run without
prompts or other manual interaction and can run in parallel with other active processes.
With the Bourne shell, you launch a background job with the & operator.
The command cc myprog.c &, for example, compiles the source program
myprog.c without tying up the terminal. You can do other work--even edit
files with a full-screen editor--while the cc command works behind the
scenes. Enhancements to the stty command and
the terminal driver in recent UNIX releases
have added a new control key to your terminal: Suspend. Suspend is usually Ctrl+Z.
This new tool enables you to take an interactive program you're currently running,
such as a vi editing session, and to put it temporarily into the background. If
the program wants to talk to your terminal, the system suspends the program.
Otherwise, it
continues running. The Korn shell adds some tools that help you
manage the family of processes you can
accumulate. These tools consist of the jobs, kill, wait, bg,
and fg commands. To use the Korn shell's job-control tools, you
must have the monitor option
enabled. Normally, the monitor option is enabled for you
automatically; it's
the default for interactive shells. If your operating system doesn't support job
management, the default for the monitor option is off. Even without operating
system support--the Suspend key and stty function are an operating system
service, not a Korn shell service--you still can use some of the Korn shell's job-control
tools, but you must set the monitor option on yourself. You do
that with the command set -o monitor. The jobs command, which takes no
arguments, simply lists the jobs that you
currently have active. The output of jobs looks like this: $ jobs[1] + Running xlogo&[2] + Running xclock -bg LightGreen&[3] + Stopped vi myprog.c You use the kill, bg, and fg
commands to manage jobs. When
referring to a job, you use the job number shown in brackets in the output of jobs,
preceded by a percent (%) sign. For example, kill %1 would
terminate the xlogo program you currently have running. The wait, kill,
bg, and fg commands also can refer to background jobs
by their Process ID, which you generally can obtain from the output of the ps command.
The use of Korn shell job numbers is preferred, however, because they are simpler
and safer to use than Process IDs. You create jobs in one of three ways: · By explicitly designating a command for background execution with the & · operator · ·
· By switching a job into the background with the Korn shell bg command · ·
· By pressing the Suspend key--usually Ctrl+Z--while a foreground program is running ·
By convention, a job started or switched into
the background continues to run until
it tries to read from your terminal. Then it is suspended by the operating system
until you intervene. When it is in this state, the jobs command
shows that
the command is Stopped. A job that has been stopped usually needs to talk
to you before it can continue. In
the previous jobs example, the vi command is shown to be
stopped. The
command won't continue until you reconnect it to your terminal. You do this
with the
fg command--for example, fg %3 or fg %vi. The vi command
then becomes the foreground process, and it resumes normal interactive
execution with
you.
NOTE: A full-screen program such as vi probably won't recognize that the screen no longer matches your last edit screen. You probably will need to press Ctrl+L to redraw the screen before you resume your edit session. Other programs that merely need your response to a prompt don't require any special action when you resume them with fg.
Table 11.17 shows the full syntax of the %
argument accepted by the wait, kill,
fg, and bg commands. Table 11.17. Job reference argument syntax.
The syntax of the Korn shell job-control
commands is summarized in the following sections. Displaying Background Jobs and Their Status Use the jobs command to
display background jobs and their status. For example, jobs [ -lp ] [ job ... ] The -l option causes the jobs
command to list the Process ID for
each job in addition to its job number. The -p option causes the jobs command
to list only the Process ID for each job instead of its job number. If you omit the job arguments, jobs
displays information about all
background jobs, as in this example: $ jobs[1] + Running xlogo&[2] + Running xclock -bg LightGreen&[3] + Stopped vi myprog.c If you include job arguments, they display
information only for the specified jobs.
For job, specify a Process ID or a job reference beginning with %. To
find out whether job 2 from the preceding example is still running, you would enter
this command: $ jobs %2[2] + Running xclock -bg LightGreen& Sending Signals to a Job Use the kill command to send a signal to
the specified jobs. Some signals cause a job to terminate. The TERM
signal--also called
signal 15 or interrupt--usually causes a job to terminate gracefully, whereas signal
9 always terminates a job but may leave files unclosed or wreak other havoc on
the job that was in progress. You should use kill -9 only when you
cannot terminate
the job any other way. The kill command generally is a UNIX
system command, but the Korn shell provides
kill as a built-in command with enhanced capabilities. The Korn shell
supports the basic functionality of the UNIX kill command
transparently. Its
syntax follows: kill [ -signal ] job ... For signal, specify a signal number or
a signal name. Signal numbers 1
through 15 are always valid. A signal name is one of a predefined list of
mnemonic symbols
that correspond to the valid signal numbers. Use kill -l to obtain a
list of the valid signal names. The names TERM (terminate) and HUP (hang-up)
are always valid. (See your UNIX User's Reference Manual for more information about
the kill and signal commands.)
NOTE: The reason for the vagueness about signal names is that they vary from one version of UNIX to another. You'll have to use kill-l to find out which names pertain specifically to your system.
For job, provide one or more Process ID
numbers or job references. Job
references begin with %. You must provide at least one job argument
with the kill command. Suppose that you have started an xclock
process, displaying a clock on your
X terminal screen: $ xclock -bg LightGreen&[4] + Running xclock -bg LightGreen& You can cancel the xclock window (a
background job) with either of the following
commands: $ kill %4 or $ kill %xclock Suspending the Shell Until a Job Finishes Use wait to suspend the
shell until the specified job, if any, finishes. The visible effect of wait is
simply to cause the shell not to issue another prompt to you. To get the
prompt back
if you decide not to wait, simply press Enter. This causes the shell to issue a
prompt, and it terminates the wait command. The syntax of the wait command
follows: wait [ job ... ] For job, specify one or more Process ID
numbers or job references that
designate the job or jobs for which you want to wait. If you specify no jobs, the
shell waits until any job finishes. If you specify two or more jobs, the
shell waits
until all the specified jobs finish. You won't use the wait command too
often, but it is convenient when you have
done all the interactive work you have and need the results of one or more
background jobs
before you continue. Without the wait command, you would have to
execute the
jobs command repeatedly until the job or jobs you want were marked Done. One situation in which the wait command
is useful is when developing some
formatted text files. You might want to run nroff or troff as
background jobs, capturing the output to a disk file for review. While the nroff or
troff job is running, you can edit other text files. When you have
no other
editing work to do, you'll need to wait for nroff or troff to
finish, because you have nothing else to do but review your previous work. A
hypothetical console
session might look like Listing 11.5. Listing 11.5. A console session.
$ vi chap1.nr$ nroff -me chap1.nr >chap1.nrf &[4] + Running nroff -me chap1.nr$ vi chap2.nr$ nroff -me chap2.nr > chap2.nrf &[5] Running nroff -me chap2.nr$ jobs[4] Running nroff -me chap1.nr[5] Running nroff -me chap2.nr$ wait In this listing, you overlapped the editing of chap2.nr
with the formatted printing
of chap1.nr. After finishing the edit of chap2.nr, you see
by running the jobs command that both nroff jobs still are running.
Because you have no more editing tasks to perform, you can use the wait command
to wait until one of the two background jobs finishes. The shell will not issue
another prompt until one of the two jobs is done. Then you'll receive a Done message: $ wait[5] Done nroff -me chap2.nr$ Another useful application of wait is
managing X sessions. When you log on
and use X, one of two files is processed: .xinitrc or .xsession. The
file processed depends on the method you used to run X. When I connect, my .xsession file
gets processed. When .xsession terminates, my X session is finished and
I get logged out. An extract from my .xsession looks like this: ctwm &WINM=$!xv -quit -root etc/pics/space.gif &xterm -sb -sl 2000 -ls -title "Xterm 1" -geometry 80x24+0+86 &xterm -sb -sl 2000 -ls -title "Xterm 2" -geometry 80x24+523+430 &wait $WINM This code uses two features of the Korn shell.
First, I use $! to get the
Process ID of the Window Manager I run, ctwm, and assign it to the
variable WINM.
I then start two xterms and set the desktop background by using
xv. Then I issue a wait command that waits for the process $WINM
to finish. In this case, WINM is the Process ID of the Window Manager,
so, in other words, after my Window Manager shuts down, my .xsession is
terminated and I get logged out. Moving Background Jobs into the Foreground Use fg to move background jobs
into the foreground. Foreground execution implies interactive processing with the
terminal. Therefore, using fg to bring more than one job into the
foreground establishes
a race condition; the first job to get your terminal wins, and the others revert
to Stopped status in the background. The syntax for fg
follows: fg [ job ... ] For job, specify one or more Process ID
numbers or job references. If
you omit job, the current background process is brought into the foreground.
The current job is the job you most recently stopped or started. The need to use the fg command often
arises as a result of actions you take
yourself. Suppose that you are editing a text file with vi and, when trying to
save the file and quit, you discover that you do not have Write permission
for the
file. You can't save the file until you correct the condition, but you're
currently stuck
inside the editor. What do you do? First, stop the vi editor session by pressing
Ctrl+Z. You'll immediately get the following
console output: [1] Stopped vi chap2.nr$ Now, determine the cause of the problem and
correct it. For the sake of brevity, assume
that the problem is nothing more than that you've tried to edit a file you've write-protected: $ ls -l chap2.nr-r--r--r-- 1 barbara user 21506 May 5 10:52$ chmod u+w chap2.nr$ ls -l chap2.nr-rw-r--r-- 1 barbara user 21506 May 5 10:52 Finally, use the fg command to bring
the vi edit session, currently stopped in
the background, back into execution: $ fg %vi You might need to type Ctrl+L (a vi editor
command) to redraw the screen. Moving Foreground Jobs into the Background Use the bg command to
place jobs currently in the Stopped status (as indicated by the jobs command)
into the background and to resume execution. Note that a job immediately switches
back to the Stopped state if it requires terminal input. The syntax for
bg follows: bg [ job ... ] For job, specify one or more Process ID
numbers or job references. A
job reference begins with %. If you omit job, the command refers
to the current job, which is the job you most recently started or stopped. In actual practice, you don't use the bg
command to move a foreground job
into the background, because there's no way to do so; the shell is not
listening to
your terminal while a foreground job is running. To get the shell's attention while
a foreground command is running, you need to use Ctrl+Z to stop (suspend) the foreground
job. After you stop the job and have a shell prompt,
you need to decide what to do with the job you stopped. You
can perform other tasks and restart the stopped job with
the fg command when finished, as described earlier. But if the job you
stopped is not interactive (if it can run without constant input from you),
you can
tell the shell to restart the job but leave it in the background. Suppose that you start a long-running format of
a text file using the troff command: $ troff -me chap1.nr > chap1.trf If, after waiting a few minutes for the job to
finish, you find that you want to
do something else instead of just sitting there, you can use the following
sequence to
switch the troff command to background execution: [ctrl-z]$ bg$ By default, the shell assumes that you mean the
job you last stopped. Now that the
troff command is running in the background, you can do other work. The net result of these actions is the same as
if you had started the troff job
in the background to begin with: $ troff -me chap1.nr > chap1.trf & Summary
This chapter presented the features of the Korn
shell. Because the Korn shell has
many features in common with the Bourne shell, only the features special to
the Korn
shell were discussed here. The Korn shell is one of several shells
available to you on most contemporary versions
of the UNIX operating system. It is a newer, enhanced version of the original Bourne
shell, with command history, command editing, command aliases, and job
control to
improve your keyboard productivity. The Korn shell also offers a number of
improvements for
the shell-script writer, including arithmetic variables and arithmetic
expressions, array
variables, a select statement for prompting the user with menus, and a
coprocess mechanism for interactively executing other UNIX commands from
within a
shell script. The initial impetus for construction of the Korn
shell was to bring many of the enhancements
in csh to users in a format consistent with the Bourne shell syntax
and behavior. The C shell (csh) was implemented by the Berkeley
group and
initially was offered only in the BSD variant of UNIX. The Korn shell ported its
extensions, with many additional improvements, into the System V environment. Many
people feel that the Korn shell is a successor to both the Bourne and C
shells. It
is now the shell of choice for use at the keyboard and for writing shell
scripts. The command-history feature enables you to capture
in a disk file each command as
you execute it. The file is preserved across logons so that you have some of
the context
of your previous session when you next log on. You can use the
command-history file
for reference or for reexecuting commands. When you reexecute a command, you can
use it as it was written originally, or you can modify it before execution.
The fc
command and the history and r aliases provide the user interface
to the command-history file. The command-editing feature provides two text
editor styles for editing commands as
you write them. You must explicitly enable command editing to use it. By
default, the
Korn shell manages the command line in the same way as the Bourne shell. The vi
Edit mode implements most of the vi input and command modes, and it enables
you to
access and reuse commands stored in the command-history file. The EMACS Edit
mode is
compatible with the EMACS editor commands. Most users find the vi or EMACS
Command-Edit mode
to be more natural than the equivalent bang (!) notation of the
C shell. The command alias feature enables you to define
new command names that stand for a
leading portion of the command line of existing commands. The definition of
an alias
can replace not only the name of an existing command but also initial options and
arguments of the command line. This feature greatly reduces the amount of
typing needed
for frequently executed commands. It also replaces the command-tracking
feature of
the Bourne shell. Extensions to wildcard file-naming patterns
provide more complex expressions that you
can use to narrow in on the specific files you want to reference. Features added for the benefit of the script
writer are numerous and powerful. They
eliminate some of the kludges you used to have to deal with when writing new commands. The typeset command provides a host of
new features surrounding the use of
shell variables. Array variables with the form ${name[n]} permit the
convenient processing of lists. Integer variables defined with typeset, the
let command, and the (( )) expression notation enable you
to perform
basic numeric calculations without having to leave the shell environment. You
no longer have to resort to command substitution for the expr or bc command. An improved syntax for command substitution
makes even this chore more palatable. The
syntax $(...) for command replacement reduces the need for quoting
substrings inside
backquoted expressions. You even can nest them, which permits expressions such
as $(...$(...)...) on the command line. Coprocessing, a new feature of the shell,
enables you to read and write from background commands,
using them in an interactive fashion. You can respond to error messages produced
by the invoked command, and you can provide a programmed response. You launch a
coprocess with the |& operator, using it in place of the & symbol.
Once launched, a coprocess runs in parallel with your shell's process. To write
to the command, use print -p. To read its output, use read -p. You
can reassign the input and output pipes by using the exec fd>&p and
exec fd<&p special commands. Now the script writer can do
things previously possible only in the C programming language. Another boon is the Privileged shell mode. You
can set the Set User ID and Set Group
ID flags on your shell scripts. You can use the set -o privileged or
set -p option to toggle between the user's real User ID and the
effective User
ID. Use this feature to write special system services--for example, a tape
library management
system, a device-allocation facility, or a file-sharing system. Remember to
exercise caution, though: Badly written scripts can give a potential attacker a
door to a more privileged user. Last but not least, the Korn shell provides a
way of getting around the problem of
not being able to export aliases and functions. By using the ENV
exported variable, you can define a
miniprofile to be executed at each invocation of the shell. You
no longer have to switch to the shell from vi, pg, or sdb only
to find a bare-bones environment without your favorite aliases and functions. All in all, the Korn shell seems to be just
about the final word in command-line environments.
Now your main concern will be whether compatibility constraints enable you
to use the Korn shell for script writing. Although the Korn shell can execute Bourne shell scripts, the Bourne shell
can't execute Korn shell scripts, and only the
C shell can execute C shell scripts. At least you're free to use the Korn
shell for
your keyboard environment, which is a step up for sure! |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|