Chapter 3. Customizing Your Environment

A common synonym for a Unix shell, or for the interface any computer program presents, is an environment. An environment is typically a collection of concepts that expresses the things a computer does in terms designed to be understandable and coherent, and a look and feel that is comfortable.

For example, your desk at work is an environment. Concepts involved in desk work usually include memos, phone calls, letters, forms, etc. The tools on or in your desk that you use to deal with these things include paper, staples, envelopes, pens, a telephone, a calculator, etc. Every one of these has a set of characteristics that express how you use it; such characteristics range from location on your desk or in a drawer (for simple tools) to more sophisticated things like which numbers the memory buttons on your phone are set to. Taken together, these characteristics make up your desk’s look and feel.

You customize the look and feel of your desk environment by putting pens where you can most easily reach them, programming your phone buttons, etc. In general, the more customization you have done, the more tailored to your personal needs — and therefore the more productive — your environment is.

Similarly, Unix shells present you with such concepts as files, directories, and standard input and output, while Unix itself gives you tools to work with these, such as file manipulation commands, text editors, and print queues. Your Unix environment’s look and feel is determined by your keyboard and display, of course, but also by how you set up your directories, where you put each kind of file, and what names you give to files, directories, and commands. There are also more sophisticated ways of customizing your shell environment.

The most basic means of customization that the Korn shell provides are these:

Aliases

Synonyms for commands or command strings that you can define for convenience.

Options

Controls for various aspects of your environment, which you can turn on and off.

Variables

Placeholders for information that tell the shell and other programs how to behave under various circumstances.

There are also more complex ways to customize your environment, mainly the ability to program the shell, which we will see in later chapters. In this chapter, we cover the techniques listed above.

While most of the customizations obtainable with the above techniques are straightforward and apply to everyday Unix use, others are rather arcane and require in-depth technical knowledge to understand. Most of this chapter concentrates on the former. Because we want to explain things from the perspective of tasks you may want to perform, rather than that of the specific features of the Korn shell, a few little details may fall through the cracks (such as miscellaneous options to certain commands). We suggest you look in Appendix B for this type of information.

The .profile File

If you want to customize your environment, it is most important to know about a file called .profile in your home (login) directory. This is a file of shell commands, also called a shell script, that the Korn shell reads and runs whenever you log in to your system.

If you use a large machine in an office or department, the odds are good that your system administrator has already set up a .profile file for you that contains a few standard things. This is one of the “hidden” files mentioned in Chapter 1; other common hidden files include .xinitrc (for the X Window System), .emacs (for the GNU Emacs editor), and .mailrc (for the Unix mail program).

Your .profile, together with the environment file that we discuss towards the end of this chapter, will be the source of practically all of the customizations we discuss here as well as in subsequent chapters. Therefore, it is very important for you to become comfortable with a text editor like vi or Emacs so that you can try whatever customization techniques strike your fancy.

Bear in mind, however, that if you add commands to your .profile, they will not take effect until you log out and log back in again, or type the command login.[32] Of course, you need not immediately add customization commands to your .profile — you can always just test them by typing them in yourself. (Be sure you test your changes though: it is possible to set things up in your .profile such that you can’t log back in! Test your changes before logging out, by logging in again, perhaps from a new window or virtual console.)

If you already have a .profile, it’s likely to contain lines similar to some of these:

PATH=/sbin:/usr/sbin:/usr/bin:/etc:/usr/ucb:/local/bin:
stty stop ^S intr ^C erase ^?
EDITOR=/usr/local/bin/emacs
SHELL=/bin/ksh
export EDITOR

These commands set up a basic environment for you, so you probably shouldn’t change them until you learn about what they do — which you will by the end of this chapter. When you edit your .profile, just put your additional lines in afterwards.

The /etc/profile File

Every user has a personal .profile file in the home directory. While your system administrator may have provided you with an initial .profile file when your account was first set up, you are free to customize it as you see fit.

There is an additional, system-wide, customization file known as /etc/profile. If this file exists, the Korn shell reads and executes it as the very first thing it does, even before reading your personal .profile file. This is where your system administrator places commands that should be executed by every user upon login, and where he or she places system-wide defaults, such as adding extra directories to the PATH variable (which, as you will see later in this chapter, tells the shell where to look for programs to run).

It pays to be aware of this file, since it may have settings in it that you might wish to override in your own .profile file. (At least, once you understand what it’s doing!) If the file exists, it will be readable and will contain shell commands in it, just like your .profile. It may be worthwhile to peruse the version on your system; you may learn something that way.

Aliases

Perhaps the easiest and most popular type of customization is the alias, which is a synonym for a command or command string. This is one of several Korn shell features that were appropriated from the C shell.[33] You define an alias by entering (or adding to your .profile) a line with the following form:

alias new=original

(Notice that there are no spaces on either side of the equal sign (=); this is required syntax.) The alias command defines new to be an alias for original; whenever you type new, the Korn shell substitutes original internally. (You cannot use any of the shell’s special characters, such as *, $, =, and so on, in alias names.)

There are a few basic ways to use an alias. The first, and simplest, is as a more mnemonic name for an existing command. Many commonly used Unix commands have names that are poor mnemonics and therefore are excellent candidates for aliasing; the classic example is:

alias search=grep

grep, the Unix file-searching utility, derives its name from the command “g/re/p” in the original ed text editor, which does essentially the same thing as grep. (The regular expression matching code was carved out of ed to make a separate program.)[34] This acronym may mean something to a computer scientist but probably not to the office administrator who has to find Fred in a list of phone numbers. If you have to find Fred, and you have the word search defined as an alias for grep, you can type:

search Fred phonelist

Another popular alias eschews exit in favor of a more widely used command for ending a login session:

alias logout=exit

If you are a C shell user, you may be used to having a .logout file of commands that the shell executes just before you log out. The Korn shell doesn’t have this feature as such, but you can mimic it quite easily using an alias:

alias logout='. ~/.ksh_logout; exit'

This executes the commands in the file .ksh_logout in your home directory and then logs you out. The semicolon acts as a statement separator, allowing you to have more than one command on the same line.

Notice the quotes around the full value of the alias; these are necessary if the string being aliased consists of more than one word.[35]

You might want the file .ksh_logout to “clean up” your history files, as we discussed in the last chapter. Recall that we created history files with names like .hist.42, which guarantees a unique name for every serial line or window. To remove these files when the shells exit, just put this line in your .ksh_logout file:

rm ~/.hist.*

Some people who aren’t particularly good typists like to use aliases for typographical errors they make often. For example:

alias emcas=emacs
alias mali=mail
alias gerp=grep

This can be handy, but we feel you’re probably better off suffering with the error message and getting the correct spelling under your fingers. Another common way to use an alias is as a shorthand for a longer command string. For example, you may have a directory to which you need to go often. It’s buried deeply in your directory hierarchy, so you want to set up an alias that will allow you to cd there without typing (or even remembering) the entire pathname:

alias cdcm='cd ~/work/projects/devtools/windows/confman'

As before, the quotes around the full cd command are needed, because the string being aliased has more than one word.

As another example, a useful option to the ls command is -F: it puts a slash (/) after directory files and an asterisk (*) after executable files. (Depending on your system, it may append other characters after other kinds of files as well.) Since typing a dash followed by a capital letter is inconvenient, many people like to define an alias like this:

alias lf='ls -F'

A few things about aliases are important to remember. First, the Korn shell makes a textual substitution of the alias for that which it is aliasing; it may help to imagine ksh passing your command through a text editor or word processor and issuing a “change” or “substitute” command before interpreting and executing it.

This, in turn, means that any special characters (such as wildcards like * and ?) that result when the alias is expanded are interpreted properly by the shell. This leads to an important corollary: wildcards and other special characters cannot be used in the names of aliases, i.e., on the left side of the equal sign. For example, to make it easier to print all of the files in your directory, you could define the alias:

alias printall='pr * | lp'

Second, keep in mind that aliases are recursive, which means that it is possible to alias an alias. A legitimate objection to the previous example is that the alias, while mnemonic, is too long and doesn’t save enough typing. If we want to keep this alias but add a shorter abbreviation, we could define:

alias pa=printall

Recursive aliasing makes it possible to set up an “infinite loop” of definitions, wherein an alias ends up (perhaps after several lookups) being defined as itself. For example, the command:

alias ls='ls -l'

sets up a possible infinite loop. Luckily, the shell has a mechanism to guard against such dangers. The above command works as expected (typing ls produces a long list with permissions, sizes, owners, etc.). Even more pathological situations work, such as these:

alias listfile=ls
alias ls=listfile

If you type listfile, ls runs.

Aliases can only be used for the beginning of a command string — albeit with certain exceptions. In the cd example above, you might want to define an alias for the directory name alone, not for the entire command. But if you define:

alias cm=work/projects/devtools/windows/confman

and then type cd cm, the Korn shell will probably print a message like ksh: cd: cm: [No such file or directory].

An obscure, rather ugly feature of the Korn shell’s alias facility — one not present in the analogous C shell feature — provides a way around this problem. If the value of an alias (the right side of the equal sign) ends in a space or a tab, then the Korn shell tries to do alias substitution on the next word on the command line. To make the value of an alias end in a space, you need to surround it with quotes.

This feature exists so that it is possible to have aliases for commands that themselves run other commands, such as nohup and nice. For example, nohup is aliased to 'nohup '. That way, when you type:

nohup my_favorite_alias somefile

the shell will expand my_favorite_alias just as it would when typed without the preceding nohup command. (The nohup command is described in Chapter 8.)

Here is how you would use this capability to allow aliases for directory names, at least for use with the cd command:

alias cd='cd '

This causes the Korn shell to search for an alias for the directory name argument to cd, which in the previous example would enable it to expand the alias cm correctly.

The Korn shell provides an efficiency feature called “tracked aliases.” We delay discussion of these until Section 3.4.2.8. Also, a number of aliases are predefined by the shell; they are listed in Appendix B.

Finally, there are a few useful adjuncts to the basic alias command. If you type alias name without an equal sign (=) and value, the shell prints the alias’s value or name : alias not found if it is undefined. If you type alias without any arguments, you get a list of all the aliases you have defined as well as several that are built-in. If you type alias -p, the shell prints all your aliases, with each one preceded by the alias keyword. This is useful for saving all your aliases in a way that allows them to be re-read by the shell at a later time. The command unalias name removes any alias definition for its argument. If you type unalias -a, the shell removes all aliases.

Aliases are very handy for creating a comfortable environment, but they are really just kid stuff compared to more advanced customization techniques like scripts and functions, which we will see in the next chapter. These give you everything aliases do plus much more, so if you become proficient at them, you may find that you don’t need aliases anymore. However, aliases are ideal for novices who find Unix to be a rather forbidding place, full of terseness and devoid of good mnemonics.

Options

While aliases let you create convenient names for commands, they don’t really let you change the shell’s behavior. Options are one way of doing this. A shell option is a setting that is either “on” or “off.” While several options relate to arcane shell features that are of interest only to programmers, those that we cover here are of interest to all users.

The basic commands that relate to options are set -o optionnames and set +o optionnames, where optionnames is a list of option names separated by whitespace. The use of plus (+) and minus (-) signs is counterintuitive: the - turns the named option on, while the + turns it off. The reason for this incongruity is that the dash (-) is the conventional Unix way of specifying options to a command, while the use of + is an afterthought.

Most options also have one-letter abbreviations that can be used in lieu of the set -o command; for example, set -o noglob can be abbreviated set -f. These abbreviations are carry-overs from the Bourne shell. Like several other “extra” Korn shell features, they exist to ensure upward compatibility; otherwise, their use is not encouraged.

Table 3-1 lists the options that are useful to general Unix users. All of them are off by default except as noted.

Table 3-1. Basic shell options
OptionDescription
bgnice

Run background jobs at lower priority (on by default).

emacs

Enter emacs editing mode.

ignoreeof

Don’t allow use of CTRL-D to log off; require the exit command.

markdirs

When expanding filename wildcards, append a slash (/) to directories.

noclobber

Don’t allow output redirection (>) to clobber an existing file.

noglob

Don’t expand filename wildcards like * and ? (wildcard expansion is sometimes called globbing).

nounset

Indicate an error when trying to use a variable that is undefined.

trackall

Turn on alias tracking. (The shell actually ignores the setting of this option; alias tracking is always turned on. This is discussed in Section 3.4.2.8, later in this chapter.)

vi

Enter vi editing mode.

There are several other options (22 in all; Appendix B lists them). To check the status of an option, just type set -o. The Korn shell prints a list of all options along with their settings. There is no command for testing single options, but here is a simple shell function to do it:

function testopt {
    if [[ -o $1 ]] ; then
        print Option $1 is on.

    else
        print Option $1 is off.
    fi
}

Shell functions are covered in the next chapter. For now, though, if you want to use the testopt function, just type it into your .profile or environment file (see Section 3.5.2, later in this chapter), type either login or . .profile. (Yes, the period, or “dot,” is actually a command; see Section 4.1 in Chapter 4.) Then you can type testopt optionname to check the status of an option.

Shell Variables

There are several characteristics of your environment that you may want to customize but that cannot be expressed as an on/off choice. Characteristics of this type are specified in shell variables. Shell variables can specify everything from your prompt string to how often the shell checks for new mail.

Like an alias, a shell variable is a name that has a value associated with it. The Korn shell keeps track of several built-in shell variables; shell programmers can add their own. By convention, built-in variables have names in all capital letters. The syntax for defining variables is somewhat similar to the syntax for aliases:

               varname=value

There must be no space on either side of the equal sign, and if the value is more than one word, it must be surrounded by quotes. To use the value of a variable in a command, precede its name by a dollar sign ($).

You can delete a variable with the command unset varname. Normally, this isn’t useful, since all variables that don’t exist are assumed to be null, i.e., equal to the empty string "". But if you use the option nounset (see Table 3-1), which causes the shell to indicate an error when it encounters an undefined variable, you may be interested in unset.

The easiest way to check a variable’s value is to use the print built-in command.[36] All print does is print its arguments, but not until the shell has evaluated them. This includes — among other things that will be discussed later — taking the values of variables and expanding filename wildcards. So, if the variable fred has the value bob, typing the following causes the shell to simply print bob:

print "$fred"

If the variable is undefined, the shell prints a blank line. A more verbose way to do this is:

print "The value of $varname is "$varname"."

The first dollar sign and the inner double quotes are backslash-escaped (i.e., preceded with so the shell doesn’t try to interpret them; see Chapter 1) so that they appear literally in the output, which for the above example would be:

The value of $fred is "bob".

Variables and Quoting

Notice that we used double quotes around variables (and strings containing them) in these print examples. In Chapter 1 we said that some special characters inside double quotes are still interpreted (while none are interpreted inside single quotes).

Perhaps the most important special character that “survives” double quotes is the dollar sign — meaning that variables are evaluated. It’s possible to do without the double quotes in some cases; for example, we could have written the above print command this way:

print The value of $varname is "$varname".

But double quotes are more generally correct.

Here’s why. Suppose we did this:

fred='Four spaces between these    words.'

Then if we entered the command print $fred, the result would be:

Four spaces between these words.

What happened to the extra spaces? Without the double quotes, the shell splits the string into words after substituting the variable’s value, as it normally does when it processes command lines. The double quotes circumvent this part of the process (by making the shell think that the whole quoted string is a single word).

Therefore the command print "$fred" prints this:

Four spaces between these    words.

This becomes especially important when we start dealing with variables that contain user or file input later on. In particular, it’s increasingly common to find directories made available on Unix systems via the network from Apple Macintosh and Microsoft Windows systems, where spaces and other unusual characters are common in filenames.

Double quotes also allow other special characters to work, as we’ll see in Chapter 4, Chapter 6, and Chapter 7. But for now, we’ll revise the “When in doubt, use single quotes” rule in Chapter 1 by adding, “...unless a string contains a variable, in which case you should use double quotes.”

Built-in Variables

As with options, some built-in shell variables are meaningful to general Unix users, while others are arcana for professional programmers. We’ll look at the more generally useful ones here, and we’ll save some of the more obscure ones for later chapters. Again, Appendix B contains a complete list.

Editing mode variables

Several shell variables relate to the command-line editing modes that we saw in the previous chapter. These are listed in Table 3-2.

The first two of these are sometimes used by text editors and other screen-oriented programs, which rely on the variables being set correctly. Although the Korn shell and most windowing systems should know how to set them correctly, you should look at the values of COLUMNS and LINES if you are having display trouble with a screen-oriented program.

Table 3-2. Editing mode variables
VariableMeaning
COLUMNS

Width, in character columns, of your terminal. The standard value is 80 (sometimes 132), though if you are using a windowing system like X, you could give a terminal window any size you wish.

LINES

Length of your terminal in text lines. The standard value for terminals is 24, but for IBM PC-compatible monitors it’s 25; once again, if you are using a windowing system, you can usually resize to any amount.

HISTFILE

Name of history file on which the editing modes operate.

EDITOR

Pathname of your favorite text editor; the suffix (macs [a] or vi) determines which editing mode to use.

VISUAL

Similar to EDITOR; if set, used in preference to EDITOR to choose editing mode.

HISTEDIT

Pathname of editor to use with the hist command.

[a] This suffix also works if your editor is a different version of Emacs whose name doesn’t end in emacs.

Mail variables

Since the mail program is not running all the time, there is no way for it to inform you when you get new mail; therefore the shell does this instead.[37] The shell can’t actually check for incoming mail, but it can look at your mail file periodically and determine whether the file has been modified since the last check. The variables listed in Table 3-3 let you control how this works.

Table 3-3. Mail variables
VariableMeaning
MAIL

Name of file to check for incoming mail (i.e., your mail file)

MAILCHECK

How often, in seconds, to check for new mail (default 600 seconds, or 10 minutes)

MAILPATH

List of filenames, separated by colons (:), to check for incoming mail

_ (underscore)

When used inside $MAILPATH, name of mail file that changed; see text for other uses

Under the simplest scenario, you use the standard Unix mail program, and your mail file is /var/mail/yourname or something similar. In this case, you would just set the variable MAIL to this filename if you want your mail checked:

MAIL=/var/mail/yourname

If your system administrator hasn’t already done it for you, put a line like this in your .profile.

However, some people use nonstandard mailers that use multiple mail files; MAILPATH was designed to accommodate this. The Korn shell uses the value of MAIL as the name of the file to check, unless MAILPATH is set, in which case the shell checks each file in the MAILPATH list for new mail. You can use this mechanism to have the shell print a different message for each mail file: for each mail filename in MAILPATH, append a question mark followed by the message you want printed.

For example, let’s say you have a mail system that automatically sorts your mail into files according to the username of the sender. You have mail files called /var/mail/you/fritchie, /var/mail/you/droberts, /var/mail/you/jphelps, etc. You define your MAILPATH as follows:

MAILPATH=/var/mail/you/fritchie:/var/mail/you/droberts:
/var/mail/you/jphelps

If you get mail from Jennifer Phelps, the file /var/mail/you/jphelps changes. The Korn shell notices the change within 10 minutes and prints the message:

you have mail in /var/mail/you/jphelps.

If you are in the middle of running a command, the shell waits until the command finishes (or is suspended) to print the message. To customize this further, you could define MAILPATH to be:

MAILPATH=
/var/mail/you/fritchie?You have mail from Fiona.:
/var/mail/you/droberts?Mail from Dave has arrived.:
/var/mail/you/jphelps?There is new mail from Jennifer.

The backslashes at the end of each line allow you to continue your command on the next line. But be careful: you can’t indent subsequent lines. Now, if you get mail from Jennifer, the shell prints:

There is new mail from Jennifer.

Within the message parts of MAILPATH, you may use the special variable _ (underscore) for the name of the file that is triggering the message:

MAILPATH='/var/mail/you/fritchie?You have mail from Fiona in $_.'
MAILPATH+=':/var/mail/you/droberts?Mail from Dave has arrived, check $_.'
MAILPATH+=':/var/mail/you/jphelps?There is new mail from Jennifer, look at $_.'

The meaning of $_ actually varies depending on where and how it’s used:

Inside the value of MAILPATH

As just described, use $_ for the name of the file that triggers a message in the value of MAILPATH.

The last argument of the last interactive command

When used on a command line entered interactively, $_ represents the last word on the previous command line:

$ print hi          
                              Run a command
hi
$ print $_          
                              Verify setting of $_
hi
$ print hello       
                              New last argument
hello
$ print $_
hello
$ print "hi there"  
                              Usage is word based
hi there
$ print $_
hi there

This usage of $_ is similar to the !$ feature of the C shell’s history mechanism.

Inside a script

When accessed from inside a shell script, $_ is the full pathname used to find and invoke the script:

$ cat /tmp/junk       
                              Show test program
print _ is $_
$ PATH=/tmp:$PATH     
                              Add directory to PATH
$ junk                
                              Run the program
_ is /tmp/junk

Prompting variables

If you have seen enough experienced Unix users at work, you may already have realized that the shell’s prompt is not engraved in stone. It seems as though one of the favorite pastimes of professional Unix programmers is thinking of cute or innovative prompt strings. We’ll give you some of the information you need to do your own here; the rest comes in the next chapter.

Actually, the Korn shell uses four prompt strings. They are stored in the variables PS1, PS2, PS3, and PS4. The first of these is called the primary prompt string; it is your usual shell prompt, and its default value is "$ " (a dollar sign followed by a space). Many people like to set their primary prompt string to something containing their login name. Here is one way to do this:

PS1="($LOGNAME)-> "

LOGNAME is another built-in shell variable, which is set to your login name when you log in.[38] So, PS1 becomes a left parenthesis, followed by your login name, followed by ")-> “. If your login name is fred, your prompt string will be "(fred)-> ".If you are a C shell user and, like many such people, are used to having a command number in your prompt string, the Korn shell can do this similarly to the C shell: if there is an exclamation point in the prompt string, it substitutes the command number. Thus, if you define your prompt string to be the following, your prompts will look like (fred 1)->, (fred 2)->, and so on:

PS1="($LOGNAME !)->"

Perhaps the most useful way to set up your prompt string is so that it always contains your current directory. Then you needn’t type pwd to remember where you are. Putting your directory in the prompt is more complicated than the above examples, because your current directory changes during your login session, unlike your login name and the name of your machine. But we can accommodate this by taking advantage of the different kinds of quotes. Here’s how:

PS1='($PWD)-> '

The difference is the single quotes, instead of double quotes, surrounding the string on the right side of the assignment. The trick is that this string is evaluated twice: once when the assignment to PS1 is done (in your .profile or environment file) and then again after every command you enter. Here’s what each of these evaluations does:

  1. The first evaluation observes the single quotes and returns what is inside them without further processing. As a result, PS1 contains the string ($PWD)-> .

  2. After every command, the shell evaluates ($PWD)->. PWD is a built-in variable that is always equal to the current directory, so the result is a primary prompt that always contains the current directory.[39]

We’ll discuss the subtleties of quoting and delayed evaluation in more depth in Chapter 7.

PS2 is called the secondary prompt string; its default value is "> " (a greater-than sign followed by a single space). It is used when you type an incomplete line and hit ENTER, as an indication that you must finish your command. For example, assume that you start a quoted string but don’t close the quote. Then if you hit ENTER, the shell prints > and waits for you to finish the string:

$ x="This is a long line,            
                     PS1 for the command
> which is terminated down here"     
                     PS2 for the continuation
$                                    PS1 for the next command

PS3 and PS4 relate to shell programming and debugging, respectively; they are explained in Chapter 5 and Chapter 9.

Using history command numbers

The current history command number is available in the HISTCMD environment variable. You can see the current history number in your prompt by placing a ! (or $HISTCMD) somewhere in the value of the PS1 variable:

$ PS1="command !> "
command 42> ls -FC *.xml
appa.xml  appd.xml  ch01.xml  ch04.xml  ch07.xml  ch10.xml
appb.xml  appf.xml  ch02.xml  ch05.xml  ch08.xml  colo1.xml
appc.xml  ch00.xml  ch03.xml  ch06.xml  ch09.xml  copy.xml
command 43>

To get a literal ! into the value of your prompt, place !! into PS1.

Terminal types

Today, the most common use of the shell is from inside a terminal emulator window displayed on the high resolution screen of a workstation or PC. However, the terminal emulator program still does emulate the facilities provided by the actual serial CRT terminals of yesteryear. As such, the shell variable TERM is vitally important for any program that uses your entire window, like a text editor. Such programs include traditional screen editors (such as vi and Emacs), pager programs likemore, and countless third-party applications.

Because users are spending more and more time within programs and less and less using the shell itself, it is extremely important that your TERM is set correctly. It’s really your system administrator’s job to help you do this (or to do it for you), but in case you need to do it yourself, here are a few guidelines.

The value of TERM must be a short character string with lowercase letters that appears as a filename in the terminfo database.[40] This database is a two-tiered directory of files under the root directory /usr/share/terminfo.[41] This directory contains subdirectories with single-character names; these in turn contain files of terminal information for all terminals whose names begin with that character. Each file describes how to tell the terminal in question to do certain common things like position the cursor on the screen, go into reverse video, scroll, insert text, and so on. The descriptions are in binary form (i.e., not readable by humans).

Names of terminal description files are the same as that of the terminal being described; sometimes an abbreviation is used. For example, the DEC VT100 has a description in the file /usr/share/terminfo/v/vt100; the GNU/Linux character-based console has a description in the file /usr/share/terminfo/l/linux. An xterm terminal window under the X Window System has a description in /usr/share/terminfo/x/xterm.

Sometimes your Unix software will not set up TERM correctly; this often happens for X terminals and PC-based Unix systems. Therefore, you should check the value of TERM by typing print $TERM before going any further. If you find that your Unix system isn’t setting the right value for you (especially likely if your terminal is of a different make than your computer), you need to find the appropriate value of TERM yourself.

The best way to find the TERM value — if you can’t find a local guru to do it for you — is to guess the terminfo name and search for a file of that name under /usr/share/terminfo by using ls. For example, if your terminal is a Blivitz BL-35A, you could try:

$ cd /usr/share/terminfo
$ ls b/bl*

If you are successful, you will see something like this:

bl35a           blivitz35a

In this case, the two names are likely to be synonyms for (links to) the same terminal description, so you could use either one as a value of TERM. In other words, you could put either of these two lines in your .profile:

TERM=bl35a
TERM=blivitz35a

If you aren’t successful, ls won’t print anything, and you will have to make another guess and try again. If you find that terminfo contains nothing that resembles your terminal, all is not lost. Consult your terminal’s manual to see if the terminal can emulate a more popular model; nowadays the odds of this are excellent.

Conversely, terminfo may have several entries that relate to your terminal, for submodels, special modes, etc. If you have a choice of which entry to use as your value of TERM, we suggest you test each one out with your text editor or any other screen-oriented programs you use and see which one works best.

The process is much simpler if you are using a windowing system, in which your “terminals” are logical portions of the screen rather than physical devices. In this case, operating system-dependent software was written to control your terminal window(s), so the odds are very good that if it knows how to handle window resizing and complex cursor motion, it is capable of dealing with simple things like TERM. The X Window System, for example, automatically sets “xterm” as its value for TERM in an xterm terminal window.

Command search path

Another important variable is PATH, which helps the shell find the commands you enter.

As you probably know, every command you use is actually a file that contains code for your machine to run.[42] These files are called executable files or just executables for short. They are stored in various directories. Some directories, like /bin or /usr/bin, are standard on all Unix systems; some depend on the particular version of Unix you are using; some are unique to your machine; if you are a programmer, some may even be your own. In any case, there is no reason why you should have to know where a command’s executable file is in order to run it.

That is where PATH comes in. Its value is a list of directories that the shell searches every time you enter a command name that does not contain a slash; the directory names are separated by colons (:), just like the files in MAILPATH. For example, if you type print $PATH, you will see something like this:

/sbin:/usr/sbin:/usr/bin:/etc:/usr/X11R6/bin:/local/bin

Why should you care about your path? There are three main reasons. First, there are security aspects to its value, which we touch on shortly. Second, once you have read the later chapters of this book and you try writing your own shell programs, you will want to test them and eventually set aside a directory for them. Third, your system may be set up so that certain “restricted” commands’ executable files are kept in directories that are not listed in PATH. For example, there may be a directory /usr/games in which there are executables that are verboten during regular working hours.

Therefore you may want to add directories to the default PATH you get when you login. Let’s say you have created a bin directory under your login directory for your own shell scripts and programs. To add this directory to your PATH so that it is there every time you log in, put this line in your .profile:

PATH="$PATH:$HOME/bin"

This sets PATH to whatever it was before, followed immediately by a colon and $HOME/bin (your personal bin directory). This is a rather typical usage. (Using $HOME lets your system administrator move your home directory around, without your having to fix your .profile file.)

There is an important additional detail to understand about how PATH works. This has to do with empty (or “null”) elements in the PATH. A null element can occur in one of three ways: placing a lone colon at the front of PATH, placing a lone colon at the end of PATH, or placing two adjacent colons in the middle of PATH. The shell treats a null element in PATH as a synonym for “.”, the current directory, and searches in whatever directory you happen to be in at that point in the path search.

PATH=:$HOME/bin:/usr/bin:/usr/local/bin   Search current directory first
PATH=$HOME/bin:/usr/bin:/usr/local/bin:   Search current directory last
PATH=$HOME/bin::/usr/bin:/usr/local/bin   Search current directory second

Finally, if you need to know which directory a command comes from, you need not look at directories in your PATH until you find it. The shell built-in command whence prints the full pathname of the command you give it as argument, or just the command’s name if it’s a built-in command itself (like cd), an alias, or a function (as we’ll see in Chapter 4).

PATH security considerations

How you set up your PATH variable can have important implications for security.

First, having the current directory in your path is a real security hole, especially for system administrators, and the root account should never have a null element (or explicit dot) in its search path. Why? Consider someone who creates a shell script named, for example, ls, makes it executable, and places it in a directory that root might cd to, such as /tmp:

rm -f /tmp/ls           Hide the evidence
/bin/ls "$@"            Run real ls
nasty stuff here        Silently run other stuff as root

If root has the current directory first in PATH, then cd /tmp; ls does whatever the miscreant wants, and root is none the wiser. (This is known in the security world as a “trojan horse.”) While less serious for regular users, there are many experts who would still advise against having the current directory in PATH.

Secondly, the safest way to add your personal bin to PATH is at the end. When you enter a command, the shell searches directories in the order they appear in PATH until it finds an executable file. Therefore, if you have a shell script or program whose name is the same as an existing command, the shell will use the existing command — unless you type in the command’s full pathname to disambiguate. For example, if you have created your own version of the more command in $HOME/bin and your PATH has $HOME/bin at the end, to get your version you will need to type $HOME/bin/more (or just ~/bin/more).

The more reckless way of resetting your path is to tell the shell to look in your directory first by putting it before the other directories in your PATH:

PATH="$HOME/bin:$PATH"

This is less safe because you are trusting that your own version of the more command works properly. But it is also risky since it might allow for trojan horses (similar to the ls example we just saw). If your bin directory is writable by others on your system, they can install a program that does something nasty.

Proper use of PATH is just one of many aspects of system security. See Chapter 10 for more details. In short, we recommend leaving the current directory out of your PATH (both implicitly and explicitly), adding your personal bin directory at the end of PATH, and making sure that only you can create, remove, or change files in your personal bin directory.

PATH and tracked aliases

It is worth noting that a search through the directories in your PATH can take time. You won’t exactly die if you hold your breath for the length of time it takes for most computers to search your PATH, but the large number of disk I/O operations involved in some PATH searches can take longer than the command you invoked takes to run!

The Korn shell provides a way to circumvent PATH searches, called a tracked alias. First, notice that if you specify a command by giving its full pathname, the shell won’t even use your PATH — instead, it just goes directly to the executable file.

Tracked aliases do this for you automatically. The first time you invoke a command, the shell looks for the executable in the normal way (through PATH). Then it creates an alias for the full pathname, so that the next time you invoke the command, the shell uses the full pathname and does not bother with PATH at all. If you ever change your PATH, the shell marks tracked aliases as “undefined,” so that it searches for the full pathnames again when you invoke the corresponding commands.

In fact, you can add tracked aliases for the sole purpose of avoiding PATH lookup of commands that you use particularly often. Just put a “trivial alias” of the form alias -t command in your .profile or environment file; the shell substitutes the full pathname itself.

For example, the first time you invoke emacs, the shell does a PATH search. Upon finding the location of emacs (say /usr/local/bin/emacs), the shell creates a tracked alias:

alias -t emacs=/usr/local/bin/emacs    Automatic tracked alias

The next time you run emacs, the shell expands the emacs alias into the full path /usr/local/bin/emacs, and executes the program directly, not bothering with a PATH search.

You can also define individual tracked aliases yourself, with the option -t to the alias command, and you can list all such tracked aliases by typing alias -t by itself. (For compatibility with the System V Bourne shell, ksh predefines the alias hash='alias -t --'; the hash command in that shell displays the internal table of found commands. The Korn shell’s tracked alias mechanism is more flexible.)

Although the shell’s documentation and trackall option indicate that you can turn alias tracking on and off, the shell’s actual behavior is different: alias tracking is always on. alias -t lists all of the automatically-created tracked aliases. However, alias -p does not print tracked aliases. This is because, conceptually, tracked aliases are just a performance enhancement; they are really unrelated to the aliases that you define for customization.

Directory search path

CDPATH is a variable whose value, like that of PATH, is a list of directories separated by colons. Its purpose is to augment the functionality of the cd built-in command.

By default, CDPATH isn’t set (meaning that it is null), and when you type cd dirname, the shell looks in the current directory for a subdirectory called dirname. Similar to PATH, this search is disabled when dirname starts with a slash. If you set CDPATH, you give the shell a list of places to look for dirname; the list may or may not include the current directory.

Here is an example. Consider the alias for the long cd command from earlier in this chapter:

alias cdcm="cd work/projects/devtools/windows/confman"

Now suppose there were a few directories under this directory to which you need to go often; they are called src, bin, and doc. You define your CDPATH like this:

CDPATH=:~/work/projects/devtools/windows/confman

In other words, you define your CDPATH to be the empty string (meaning the current directory, wherever you happen to be) followed by ~/work/projects/devtools/windows/confman.

With this setup, if you type cd doc, then the shell looks in the current directory for a (sub)directory called doc. Assuming that it doesn’t find one, it looks in the directory ~/work/projects/devtools/windows/confman. The shell finds the doc directory there, so you go directly to it.

This works for any relative pathname. For example, if you have a directory src/whizprog in your home directory, and your CDPATH is :$HOME (the current directory and your home directory), typing cd src/whizprog takes you to $HOME/src/whizprog from anywhere on the system.

This feature gives you yet another way to save typing when you need to cd often to directories that are buried deep in your file hierarchy. You may find yourself going to a specific group of directories often as you work on a particular project, and then changing to another set of directories when you switch to another project. This implies that the CDPATH feature is only useful if you update it whenever your work habits change; if you don’t, you may occasionally find yourself where you don’t want to be.

Miscellaneous variables

We have covered the shell variables that are important from the standpoint of customization. There are also several that serve as status indicators and for various other miscellaneous purposes. Their meanings are relatively straightforward; the more basic ones are summarized in Table 3-4.

The first two variables are set by the login program, before the shell starts. The shell sets the value of the next two whenever you change directories. The final variable’s value changes dynamically, as time elapses. Although you can also set the values of any of these, just like any other variables, it is difficult to imagine any situation where you would want to.

Table 3-4. Status variables
VariableMeaning
HOME

Name of your home (login) directory. This is the default argument for the cd command.

SHELL

Pathname of the shell that programs should use to run commands.

PWD Current directory.
OLDPWD Previous directory before the last cd command.
SECONDS Number of seconds since the shell was invoked.

Customization and Subprocesses

Some of the variables discussed above are used by commands you may run — as opposed to the shell itself — so that they can determine certain aspects of your environment. The majority, however, are not even known outside the shell.

This dichotomy begs an important question: which shell “things” are known outside the shell, and which are only internal? This question is at the heart of many misunderstandings about the shell and shell programming. Before we answer, we’ll ask it again in a more precise way: which shell “things” are known to subprocesses? Remember that whenever you enter a command, you are telling the shell to run that command in a subprocess; furthermore, some complex programs may start their own subprocesses.

The answer is actually fairly simple. Subprocesses inherit only environment variables. They are available automatically, without the subprocess having to take any explicit action. All the other “things” — shell options, aliases, and functions — must be made explicitly available. The environment file is how you do this. Furthermore, only interactive shells process the environment file. The next two sections describe environment variables and the environment file, respectively.

Environment Variables

By default, only one kind of thing is known to all kinds of subprocesses: a special class of shell variables called environment variables. Some of the built-in variables we have seen are actually environment variables: HISTFILE, HOME, LOGNAME, PATH, PWD, OLDPWD, SHELL, and TERM.

It should be clear why these and other variables need to be known by subprocesses. We have already seen the most obvious example: text editors like vi and Emacs need to know what kind of terminal you are using; TERM is their way of determining this. As another example, most Unix mail programs allow you to edit a message with your favorite text editor. How does mail know which editor to use? The value of EDITOR (or sometimes VISUAL).

Any variable can become an environment variable, and new variables can be created that are environment variables. Environment variables are created with the command:

export varnames

(varnames can be a list of variable names separated by whitespace.) If the names in varnames already exist, then those variables become environment variables. If they don’t, the shell creates new variables that are environment variables.

With ksh, you may assign a value and export the variable in one step:

export TMPDIR=/var/tmp

You can also define variables to be in the environment of a particular subprocess (command) only, by preceding the command with the variable assignment, like this:

                  varname=value command

You can put as many assignments before the command as you want.[43] For example, assume you’re using the Emacs editor. You are having problems getting it to work with your terminal, so you’re experimenting with different values of TERM. You can do this most easily by entering commands that look like:

TERM=trythisone emacs filename

emacs has trythisone defined as its value of TERM, yet the environment variable in your shell keeps whatever value (if any) it had before. This syntax is not very widely used, so we won’t see it very often throughout the remainder of this book.

Nevertheless, environment variables are important. Most .profile files include definitions of environment variables; the sample .profile earlier in this chapter contained two such definitions:

EDITOR=/usr/local/bin/emacs
SHELL=/bin/ksh
export EDITOR SHELL

For some reason, the Korn shell doesn’t make EDITOR an environment variable by default. This means, among other things, that mail will not know which editor to use when you want to edit a message.[44] Therefore you would have to export it yourself by using the export command in your .profile.

The second line in the previous code is meant for systems that do not have the Korn shell installed as the default shell, i.e., as /bin/sh. Some programs run shells as subprocesses within themselves (e.g., many mail programs and the Emacs editor’s shell mode); by convention, they use the SHELL variable to determine which shell to use.

You can find out which variables are environment variables and what their values are by typing export without arguments.

The Environment File

Although environment variables are always known to subprocesses, the shell must be explicitly told which other variables, options, aliases, etc., are to be communicated to subprocesses. The way to do this is to put all such definitions in a special file called the environment file instead of your .profile.

You can call the environment file anything you like, as long as you set the environment variable ENV to the file’s name. The usual way to do this is as follows:

  1. Decide which definitions in your .profile you want to propagate to subprocesses. Remove them from .profile and put them in a file you designate as your environment file.

  2. Put a line in your .profile that tells the shell where your environment file is:

    ENV=envfilename
    export ENV

    It is important that the value of ENV be exported, so that shell subprocesses are able to find it.

  3. For the changes to take effect immediately, logout and then log back in again.[45] (You can’t just use . ~/.profile; the shell does not rerun the $ENV file when the value of ENV changes.)

The idea of the environment file comes from the C shell’s .cshrc file; thus, many Korn shell users who came from the C shell world call their environment files .kshrc. (The rc suffix for initialization files is practically universal throughout the Unix world. It stands for “run commands” and entered the Unix lexicon by way of MIT’s Compatible Time Sharing System (CTSS)).

As a general rule, you should put as few definitions as possible in .profile and as many as possible in your environment file. Because definitions add to rather than take away from an environment, there is little chance that they will cause something in a subprocess not to work properly. (An exception might be name clashes if you go overboard with aliases.)

The only things that really need to be in .profile are commands that aren’t definitions but actually run or produce output when you log in. Option and alias definitions should go into the environment file. In fact, there are many Korn shell users who have tiny .profile files, e.g.:

stty stop ^S intr ^C erase ^?
date
from
export ENV=~/.kshrc

(The from command, in some versions of Unix, checks if you have any mail and prints a list of message headers if you do.) Although this is a small .profile, this user’s environment file could be huge.

There is an important difference between ksh88 and ksh93. In ksh88, the environment file is always executed. In ksh93, only interactive shells (those not reading from a script, but rather from a terminal) execute the environment file. Thus, it is best that the environment file contain only commands that are useful for interactive use, such as alias and option settings.

Another difference between the two shell versions is that ksh88 only does variable substitution on the value of ENV, while ksh93 does variable, command, and arithmetic substitution on its value. (Command substitution is described in Chapter 4. Arithmetic substitution is described in Chapter 6.)

Customization Hints

You should feel free to try any of the techniques presented in this chapter. The best strategy is to test something out by typing it into the shell during your login session; if you decide you want to make it a permanent part of your environment, add it to your .profile.

A nice, painless way to add to your .profile without going into a text editor makes use of the print command and one of the Korn shell’s editing modes. If you type a customization command in and later decide to add it to your .profile, you can recall it via CTRL-P or CTRL-R (in emacs-mode) or j, -, or / (vi-mode). Let’s say the line is:

PS1="($LOGNAME !)->"

After you recall it, edit it so that it is preceded by a print command, surrounded by single quotes, and followed by an I/O redirector that (as you will see in Chapter 7) appends the output to ~/.profile:

print 'PS1="($LOGNAME !)->"' >> ~/.profile

Remember that the single quotes are important because they prevent the shell from trying to interpret things like dollar signs, double quotes, and exclamation points.

You should also feel free to snoop around other peoples’ .profile files for customization ideas. A quick way to examine everyone’s .profile is as follows: let’s assume that all login directories are under /home. Then you can type:

cat /home/*/.profile > ~/other_profiles

and examine other people’s .profile files with a text editor at your leisure (assuming you have read permission on them). If other users have environment files, the file you just created will show what they are, and you can examine them as well.

Finally, be sure that no one else but you has write permission on your .profile and environment files.



[32] This has the same effect as logging out and logging in again, although it actually replaces your login session with a new one without explicitly terminating the old session.

[33] C shell users should note that the Korn shell’s alias feature does not support arguments in alias expansions, as C shell aliases do.

[34] Thanks to Dennis Ritchie and Brian Kernighan of Bell Labs for verifying this for me. ADR.

[35] This contrasts with C shell aliases, in which the quotes aren’t required.

[36] The Korn shell supports the old command echo, which does much the same thing, for backward compatibility reasons. However, we strongly recommend print because its options are the same on all Unix systems, whereas echo’s options differ among different Unix versions. This is not likely to change; the POSIX standard says that echo’s options are implementation-defined.

[37] The commonly available biff command does a better job of this; while the Korn shell only prints “you have mail” messages right before it prints command prompts, biff can tell you who the mail is from.

[38] Some very old systems use USER instead. Thankfully, such systems are becoming more and more rare with time.

[39] The shell also does command and arithmetic substitution on the value of PS1, but we haven’t covered those features yet. See Chapter 6.

[40] Versions of Unix not derived from System V use termcap, an older-style database of terminal capabilities that uses the single text file /etc/termcap for all terminal descriptions. Modern systems often have both the /etc/termcap file and the terminfo database available. Current BSD systems use a single-file indexed database, /usr/share/misc/termcap.db.

[41] This is the typical location on modern systems. Older systems have it in /usr/lib/terminfo.

[42] Unless it’s a built-in command (like cd and print), in which case the code is simply part of the executable file for the entire shell.

[43] There is an obscure option, keyword, that (if turned on) lets you put this type of environment variable definition anywhere on the command line, not just at the beginning.

[44] Actually, it will default to the line editor ed. You don’t want that, now, do you?

[45] This assumes that the Korn shell is defined as your login shell. If it isn’t, you should have your system administrator install it as your login shell.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.78.252