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:
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.
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.
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.
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:
aliasnew
=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.
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.
Option | Description |
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 |
markdirs |
When expanding filename wildcards, append a slash
( |
noclobber |
Don’t allow output redirection ( |
noglob |
Don’t expand filename wildcards like |
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.
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".
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.”
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.
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.
Variable | Meaning |
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
( |
VISUAL
|
Similar to |
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 |
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.
Variable | Meaning |
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 ( |
_ (underscore) |
When used inside |
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:
As just described, use $_
for the name of the
file that triggers a message in the value of MAILPATH
.
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.
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
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:
The first evaluation observes the single quotes
and returns what is inside them without further processing.
As a result, PS1
contains the
string ($PWD)->
.
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.
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
.
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.
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).
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.
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.
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.
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.
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.
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.
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:
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.
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.
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.)
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.