Jim Mohr's SCO Companion


Copyright 1996-1998 by James Mohr. All rights reserved. Used by permission of the author.

Be sure to visit Jim's great Linux Tutorial web site at https://www.linux-tutorial.info/

Shells and Basic Utilities

Most UNIX users are familiar with "the shell." This is where you input commands and get output on your screen. Often, the only contact users have with the shell is logging in and immediately starting some application. Some administrators, however, have modified the system to the point where users may never even see the shell or in extreme cases have eliminated the shell completely for the users.

If you never get to the point where you can actually input commands and see their output, then this chapter may be a waste of time. If your only interaction with the operating system is logging in, then most of this entire book can only serve to satisfy your curiosity. However, if you are like most users, understanding the basic workings of the shell will do wonders to improve your ability to use the system to it's fullest extent.

Up to this point, we have referred to the shell as an abstract entity. In fact, that's the way it is often referred to as there are many different shells that you can use. Each has it's own characteristics (or even quirks), but all behave in the same general fashion. Since the basic concepts are the same, I will avoid talking about specific shells until later.

In this chapter, we are going to cover the basic aspects of the shell. We'll talk about issue commands and how they system responds. Along with that, we'll cover how commands can be made to interact with each other to provide you with the ability to make your own commands. We'll also talk about the different kinds of shells, what each has to offer and some details of that particular shell behaves.

Talking to SCO UNIX: The Shell

As I mentioned in chapter 1, the shell is essentially the users' interface to the operating system. The shell is a Command Line Interpreter. Through it, you issue commands that are interpreted by the system and certain actions are carried out. Often the state where the system is sitting at a prompt, waiting for you to type input is referred to (among other things) as being at the shell prompt or at the command line.

For many years before the invention of graphic user interfaces such at X-Windows, the only way to input commands to the operating system was through a command line interpreter, or shell. In fact, shells themselves were thought of as wondrous things during the early days of computers as prior to them, users had no direct way to interact with the operating system.

Most shells, be it under DOS, UNIX, VMS, or other operating systems, have the same input characteristics. In order to get the operating system to do anything, you have to give it a command. Some commands, such as the date command under UNIX, do not require anything else to get them to work. If you type in date and press return, that's what appears on your screen: the date. (Among other things)

Some commands need something else to get them to work. This is called an argument. Some commands, like mkdir (Used to create directories) can work with only one argument. As in mkdir directory_name. Others, like cp (to copy files) require multiple arguments. As in cp file1 file2.

In many cases, you can pass flags to commands to change their behavior. These flags are generally referred to as options. For example, if you wanted to create a series of sub-directories without creating every one individually you could run mkdir with the -p option like this:

mkdir -p one/two/three/four.

In principle, anything added to the command line after the command itself is an argument to that command. Using the terminology above, some arguments are optional and some options are required. The convention is that an option changes the behavior whereas an argument is acted upon by the command. Generally options are preceded by a dash (-), whereas arguments are not. I've said it before and I will say it again, nothing is certain when it comes to UNIX. By realizing that these two terms are often interchanged, you won't get confused when you come across one or the other. I will continue to use option to reflect something that changes the command's behavior and argument to indicate something that is acted upon.

Each program or utility has its own set of arguments and options, so you will have to look at the manual pages or man-pages for the individual commands. You can call this up from the command line by typing in:

man <command_name>

Where <command_name> is the name of the command you want information about. Also, if you are not sure what the command is, OpenServer has the whatis command that will give you a brief description of that command.

Many commands require that the option appear immediately after the command and before any arguments. Others have options and arguments interspersed. Again, look at the man-page for the specifics of a particular command.

If you need a quick reminder what a command does, you can use the whatis command. This gives you a one line description of the command. For more details see the whatis(C) man-page.

Often times you just need a quick reminder as to what the available option are and what they syntax is. Rather than going through the hassle of calling up the man-page, a quick way is to get the command to give you a usage message. As its name implies, a usage message reports the usage of a particular command. I normally use -? as the option to force the usage message as I cannot think of a command where -? is a valid option.

The Search Path

It may happen that you know there is a program by a particular name on the system, but when you try to start it from the command line, you are told that the file is not found. Since you just ran it yesterday, you assume it has gotten removed or you really don't remember the spelling.

The most common reason for this is that the program you want to start is not in your search path. Your search path is a pre-defined set of directories in which the system looks for the program you type in from the command line (or is started by some other command). This saves time since the system does not have to look through every directory trying to find the program. Unfortunately, if the program is not in one of the directories specified in your path, the system cannot start the program unless you explicitly tell it where to look. To do this you would have to specify either the full path of the command or a path relative to where you are currently located.

Let's look at this issue for a minute. Think back to our discussion of files and directories. I mentioned that every file on the system can be referred to by a unique combination of path and file name. This applies to executable programs as well. By inputting the complete path, you can run any program, whether it is in you path or not.

Let's take a program that is in everyone's path like date. (At least it should be) The date program resides in the /bin directory, so it's full path is /bin/date. If you wanted to run it, you could type in /bin/date, press enter and you might get something that looks like this:

Sat Jan 28 16:51:36 PST 1995

However, since date is in your search path, you need only input its name without the path to get it to run.

You could also start a program by referencing it through a relative path. This is simply the path in relation to your current working directory. In order to understand the syntax of relative paths we need to backtrack a moment. As I mentioned you can refer to any file or directory by specifying the path to that directory. Since they have special significance, there is a way of referring to either your current directory or it's parent directory. The current directory is referenced by '.' and it's parent by '..'. (Often referred to in conversation as 'dot' and 'dot-dot'.)

Since directories are separated from files and other directories by a /, a file in the current directory could be referenced as ./file_name and a file in the parent directory would be referenced as ../file_name. You can reference the parent of the parent by just tacking on another ../, and then continue on to the root directory if you want. So the file ../../file_name is in a directory two levels up from your current directory. This slash (/) is referred to as a forward slash as compared to a back-slash (\) which is used in DOS to separate path components.

When interpreting your command line, shell interprets everything up to the first / as a directory name. Assume that we were in the root (upper-most) directory. We could access date in one of several ways. The first two, date and /bin/date we already know about. Using the fact that ./ refers to the current directory means we could also get to it like this: ./bin/date. This is saying relative to our current directory (./) look in the bin sub-directory for the command date. If we were in the /bin directory, could start the 'date' command like this: ./date. This is useful when the command you want to execute is in your current directory, but the directory is not in your path. (More on that in a moment.)

We can also get the same results from the root directory by starting the command like this: bin/date. If there is a ./ at the beginning, it knows that everything is relative to the current directory. If all that's there is a /, the system knows that everything is relative to the root directory. If no slash is at the beginning, the system searches until it gets to the end of the command or encounters a slash whichever comes first. If there is a '/' there (like in our example) it translates this to be a sub-directory of the current one. So executing the command bin/date is translate the same as ./bin/date.

Let's now assume that we were in our home directory, /usr/jimmo (for example). We could obviously access the date command simply as date since it's in our path. However, to access it by a relative path, we could say ../../bin/date. The first ../ moves up one level into /usr. The second ../ moves us up another level to /. From there we look in the sub-directory bin for the command date. Keep in mind that throughout this whole process, our current directory does not change. We are still in /usr/jimmo.

Searching your path is only done for commands. If we were to enter vi (vi is a text editor) file_name and there was no file called file_name in our current directory, vi would start editing a new file. If we had a sub-directory called text where file_name was, we would have to access either as vi ./text/file_name or vi text/file_name. Of course, we could access it with the absolute path of vi /usr/jimmo/text/file_name.

One problem that regularly crops up for users coming from a DOS environment is that the only place UNIX looks for commands is in your path. However, even if not specified in your path, the first place DOS looks is your current directory. This is not so for UNIX. UNIX only looks in your path.

For most users this is not a problem as the current directory is included in your path by default. Therefore, the shell will still be able to execute something in your current directory. Root does not have the current directory in its path. In fact, this is the way it should be. If you want to include the current directory in root's path, make sure it is the last entry in the path so that all "real" commands are executed before any one a users might try to "force" on you.

Assume a malicious user created program in his directory called more that actually did something bad. If root were to run more in that user's directory, what would get run is the incorrect one with potentially disastrous results. (Note that the current directory normally always appears at the end of the search path. So, even if there was a program called more in the current directory, the one in /usr/bin would probably get executed first. However, you can see how this could cause problems for root.)

It is common to have people working on SCO systems that have never worked on a computer before or have only worked in pure windowing environments like on a Macintosh. When they get to the command line they are lost. On more than one occasion, I have talked to customers where I have asked them to type in cd /. There is a pause and I hear: click-click-click-click-click-click-click-click-click-click-click-click "Hmmm," I think to myself, "that's too many characters." So I ask them what they typed. They respond with: "cdspaceslash"

There are some conventions we need to adhere throughout this book to in order to make things easier. One is that commands that I talk about will be in your path unless I say otherwise. Therefore, to access them all you need to do is input the name of the command without the full path.

The second convention is the translation of the phrases "input the command", "enter the command" and "type in the command." These are translated to mean "input/enter/type in the command and press enter." I don't know how many times I have talked with customers and have said "type in the command ..." and then ask them for what happens and their response is "Oh, you want me to press enter?" Yes! Unless I say otherwise, always press enter after inputting, entering or typing in a command.

All this time we have been talking about finding and executing commands, but there is one issue that I haven't mentioned. That is the concept of permissions. In order to access a file, you need to have permission to do so. If you want to read a file, you need to have read permission. If you want to write to a file you need to have write permission. If you want to execute a file you have to have execute permission.

Permissions are set on a file using the chmod command or when the file is created. The details of which I will save for later. You can read the permissions on a file by using either the l command or ls -l. At the beginning of each line will be ten characters, which can either be a dash or a letter. The first position is the type of file, whether it is a regular file (-), a directory (d), a block device file (b) and so on. Below are some examples of the various file types.














0, 0









1, 0































/etc/custom ->/opt/K/SCO/


- - regular file

c - character device

b - block device

d - directory

p - named pipe

l - symbolic link

What each of these are, we'll get into details later. If you are curious about the format of each entry, you can look at the ls(C) man-page for more details.

The next nine positions are broken into three groups. Each group consists of three characters indicating the permissions. They are, in order: read(r), write(w) and execute(x). The first group indicates what permissions the owner of the file has. The second group indicates the permissions for the group of that file. The last group indicates the permissions for everyone else.

If a particular permission was not given, there will be a dash (-) her. For example: rwx means all three permissions have been given. In our example above, the symbolic link /etc/custom has read, write and execute permissions for everyone. The device nodes /dev/tty01 and /dev/hd00 have permissions rw- only for the owner, this means only read and write, but not execute permissions have been given. The directory /bin has read and execute permissions for everyone (r-w), but only the owner can write to it (rwx).

Figure 0-1 Break-down of file permissions

For directories, the situation is slightly different than for regular files. If you do not have read permission on a directory, you cannot read the contents of that directory. Also if you do not have write permissions on a directory, you cannot write it. This means that you cannot create a new file in that directory. Execute permissions on a directory mean that you can search it. That is, if even if you could not see the contents of a particular directory, you could still access a file there if you new it's name.

Write permission on a directory also has an interesting side effect. Since you need to have write permissions on a directory to create a new file, you also need to have write permissions to remove an existing file. Even if you do not have write permissions on the file itself, if you can write to the directory, you can erase the file.

At first this sounds odd. However, remember that a directory is nothing more than a file in a special format. If you have write permissions to that directory-file, you can remove the references to other files, thereby removing them.

Shell Variables

Before we talk about specific shells, there are some general shell concepts we need to talk about. The first thing we should talk about is the shell's "environment". This is all the information that the shell will use as it runs. This includes such things as your command search path, your logname (the name you logged in under), even the terminal type you are using. Collectively they are referred to as your environment variables and individually as the "so-and-so" environment variable, such as the TERM environment variable which contains the type of terminal you are using.

When you login, most of these are set for you in one way or another. (The mechanism that sets all environment variables is shell dependent, so we will talk about it when we get to the individual shells.) Each can be viewed by simply typing echo $VARIABLE. For example, if I type: echo $LOGNAME, I get:


Typing echo $TERM, I get:


One of the most important environment variables is the PATH variable. Remember that the PATH tells the shell where it needs to look when determining what command it should run. One of the things the shell does to make sense of your command is to find out exactly what program you meant. This is done by looking for the program in the places specified by your PATH variable.

Although it is more accurate say that the shell looks in the directories specified by your PATH environment variable, it is commonly said that the shell "searches your path.” Since this is easier to type, I am going to use that convention here.

If you were to specify a path in the command name, the shell does not use your PATH variable to do any searching. That is, if you issued the command bin/date, the shell would interpret that to mean that you wanted to execute the command date that was in the bin sub-directory of your current directory. If you were in / (the root directory), all would be well and it would effectively execute /bin/date. If you were somewhere else, the shell might not be able to find anything that matched.

If you do not specify any path (that is, the command does not contain any /'s) the system will search through your path. If it finds the command, great, if not you get a message saying the command was not found.

Let's take a closer look at how that works by looking at my path variable. From the command line, if I type echo $PATH, I get:

/bin:/usr/bin:/usr/dbin:/usr/ldbin:/usr/jimmo/bin:. Ü watch the dot!!

If I type in date, the first place that the shell looks is the /bin directory. Since that's where date resides, it is executed as /bin/date. If I type in vi, the shell looks in /bin, doesn't find it, then looks in /usr/bin, where it does find vi. Now I type in getdev. ( This is a program I wrote to translated major device numbers into the driver name. Don't worry if you don't know what a major number is. You will.) The shell looks in /bin and doesn't find it. It then looks in /usr/bin. Still not there. It then tries /usr/dbin and /usr/ldbin and still can't find it. When it finally gets to /usr/jimmo/bin it finds the getdev command and executes it. (Note that since I wrote this program, you probably won't have it on your system.)

What happens if I had not yet copied the program into my personal bin directory? Well, if the getdev program is in my current directory, the shell finds a match with that last . in my path. (Remember that the . is translated to the current directory so the program is executed as ./getdev.) If that final . was missing or the getdev program was somewhere else, the shell could not find it and would tell me so with something like:

getdev: not found

Regular Expressions and Metacharacters

Often the arguments that you pass to commands are file names. For example, if you wanted to edit a file called letter, you could enter the command vi letter. In many cases, typing the entire name is not necessary. Built into the shell are special characters that it will use to expand the name. These are called metacharacters.

The two most common metacharacters are *. The * is used to represent any number of characters including zero. For example, if we have a file in our current directory called letter and we input vi let* the shell would expand this to be vi letter. Or if we had a file simply called let, this would match as well.

Instead, what if we had several files called letter.chris, letter.daniel and letter.david? The shell would expand them all out to give me the command:

vi letter.chris letter.daniel letter.david

We could also type in vi letter.da* which would be expanded to:

vi letter.daniel letter.david

If we only wanted to edit the letter to chris, we could type it in as vi *chris. However, if there were two files letter.chris and note.chris, the command vi *chris would have the same results as if we typed in:

vi letter.chris note.chris

In other words, no matter where the asterisk appears, the shell will expand it to match every name it finds. If files existed in my current directory with matching names, the shell would expand them properly. However, if there are no matching names, then file name expansion cannot take place and the file name is taken literally.

For example, if there was no file name in our current directory that began with letter, then the command vi letter* could not be expanded so we would end up editing a new file called (literally) letter*, including the asterisk. Not what we wanted.

What if we have a sub-directory called letters? If there were the three files letter.chris, letter.daniel, and letter.david, we could get to them by typing vi letters/letter*. This would expand to be:

vi letters/letter.chris letters/letter.daniel letters/letter.david

The same rules for path names with commands also apply with files names. The command vi letters/chris.letter is the same as vi ./letters/letter.chris which is the same as vi /usr/jimmo/letters/letter.chris. This is because the shell is doing the expansion before it is passed to the command. Therefore, even directories are expanded. Therefore, the command vi le*/letter.* could be expand as both letters/letter.chris and lease/letter.joe.

The next wildcard is ?. This is expanded by the shell as one, and only one, character. For example, the command vi letter.chri? is the same as vi letter.chris. However, if we were to type in vi letter.chris? (note that the ? comes after the s in chris), the result would be that we would begin editing a new file called (literally) letter.chris?. Again, not what we wanted. Also if there were two files letter.chris1 and letter.chris2, the command vi letter.chris? would be the same as:

vi letter.chris1 letter.chris2

Another commonly used metacharacter is actually a pair or characters: [ ]. The square brackets are used to represent a list of possible characters. For example, if we were not sure whether our file was called letter.chris or letter.Chris We could type in the command as: vi letter.[Cc]hris. So, no matter if the file was called letter.chris or letter.Chris we would find it. What happens if both files exists? Just as with other metacharacters both are expanded and passed to vi. Note that in this example vi letter.[Cc]hris is the same as vi letter.?hris, but it is not always so.

The list that appears inside the '[ ]' does not have to be upper- and lowercase versions of the same letter. They can be any letter, number or even punctuation. (Note that some punctuation marks have special meaning, such as *,?, [,] which we will cover shortly) For example, if we have five files, letter.chris1 - letter.chris5, we could edit to all of them with vi letter.chris[12435].

An nice thing about this list is that if it is consecutive, we don't need to list them all. Instead, we can use a dash (-) inside the brackets to indicate that we meant a range. So, the command vi letter.chris[12345] could be shortened to be vi letter.chris[1-5]. What if we only wanted, the first three and the last one? No problem, we could specify it as vi letter.chris[1-35]. This does not mean that we want files letter.chris1 through letter.chris35! Rather, we want letter.chris1, letter.chris2, letter.chris3, and letter.chris5. This is because these are all seen as individual characters.

Inside the brackets, we are not limited to just numbers or just letters. we can use both. The command 'vi letter.chris[abc123]' has the potential for editing six files: letter.chrisa, letter.chrisb, letter.chrisc, letter.chris1, letter.chris2, and letter.chris3.

If we are so inclined, we can mix and match any of these metacharacters any way we want. we can even use them multiple times in the same command. Let's take as an example the command vi *.?hris[a-f1-5]. Should they exists in our current directory, that would match all of the following:

letter.chrisa note.chrisa letter.chrisb note.chrisb letter.chrisc

note.chrisc letter.chrisd note.chrisd letter.chrise note.chrise

letter.chris1 note.chris1 letter.chris2 note.chris2 letter.chris3

note.chris3 letter.chris4 note.chris4 letter.chris5 note.chris5

letter.Chrisa note.Chrisa letter.Chrisb note.Chrisb letter.Chrisc

note.Chrisc letter.Chrisd note.Chrisd letter.Chrise note.Chrise

letter.Chris1 note.Chris1 letter.Chris2 note.Chris2 letter.Chris3

note.Chris3 letter.Chris4 note.Chris4 letter.Chris5 note.Chris5

Also, any of these names without the leading letter or note would match. Or if we wanted and issues the command: vi *.d*, this would match:

letter.daniel note.daniel letter.david note.david

Remember a moment ago, I said that the shell expands the metacharacters only with respect to the name specified. This obviously works for file names as I described above, however, it also works for command names as well.

If we were to type dat* and there was nothing in our current directory that started with dat, we would get a message like:

dat*: not found

However, if we were to type /bin/dat*, the shell can successfully expand this to be /bin/date, which it would then execute. The same applies to relative paths. If we were in / and entered ./bin/dat* or bin/dat* both would be expanded properly and the right command would be executed. If we enter the command, /bin/dat[abcdef] we get the right response as well since the shell tries all six and finds a match with /bin/date.

An important thing to note is that the shell expands as long as it can before it attempts to interpret that command. I was reminded of this fact by accident when I input bin/l*. This yielded the output:













































































































At first, I expected each one of the files in /bin that began with an l (ell) to be executed. Then I remembered that expansion takes place before the command is interpreted. Therefore, the command that I input, /bin/l* was expanded to be:

/bin/l /bin/lc /bin/ld /bin/lf /bin/line /bin/list /bin/ln /bin/login /bin/lone-tar

/bin/lorder /bin/lr /bin/ls /bin/lx

Since /bin/l was the first command in that list, I ended up with a long listing of all the files in that list. The same kind of thing happened when I input /bin/l? . However, the output looked like this:

/bin/ld /bin/lf /bin/ln /bin/lr /bin/ls /bin/lx

This is because the command was interpreted to be:

/bin/lc /bin/ld /bin/lf /bin/ln /bin/lr /bin/ls /bin/lx

Or, when I input /bin/l[abcdef], I got:

/bin/ld /bin/lf

This was expanded as:

/bin/lc /bin/ld /bin/lf

Keep in mind that the shell interprets the first thing on each line as being the command. The only way for the shell to see each of these as individual commands would be to type them all out with a semi-colon (;) to separate them. For example,

/bin/lc; /bin/ld; /bin/lf

This would cause the system to execute /bin/lc followed by /bin/ld and then by /bin/lf.

I first learned about this aspect of shell expansion after about a couple of hours of trying to extract a specific sub-directory from a tape that I had made with the cpio command. Since I made the tape using absolute paths, I attempted to restore the files as /usr/jimmo/letters/*. Rather than restoring the entire directory as I expected, it did nothing. It worked its way through the tape until it got to end then rewound itself without extracting any files.

At first I assumed I made a typo so I started all over. This time checking the command before I sent it on it's way. After half an hour or so of whirring, the tape was back at the beginning. Still no files.

Then it dawned on me, I hadn't told the cpio to overwrite existing files unconditionally. So I started it all over again.

Now those of you who know cpio realize that this wasn't the issue either. At least not entirely. When the tape got to the right spot it started overwriting everything in the directory (as I told it to). However, the files that were missing (the ones that I really wanted to get back) were still not copied from the backup tape.

The next time, I decided to just get a listing of all the files on the tape. Maybe the files I wanted were not on this tape. After a while it reached the right directory and lo and behold, there were the files that I wanted. I could see them on the tape, I just couldn't extract them.

Well, the first idea that popped into my mind was to restore everything. That's sort of like fixing a flat by buying a new car. Then I thought about restoring the entire tape into a temporary directory where I could then get the files I wanted. Even if I had the space, this still seemed like the wrong way of doing things.

Then it hit me. I was going about it all the wrong way. The solution was to go ask someone what I was doing wrong. I asked one of the more senior engineers (I had only been there less than a year at the time). When I mentioned that I was using wildcards, it was immediately obvious what I was doing wrong. (Obvious to him, not to me).

Let's think about it for a minute. It is the shell that does the expansion, not the command itself. Like when I ran /bin/l*. The shell interprets the command as starting with /bin/l. Therefore, I get the listing of all the files in /bin that start with 'l'. With cpio , the situation is similar.

When I first ran it, the shell interpreted the files (/usr/jimmo/data/*) before passing them to cpio. Since I hadn't told cpio to overwrite the files, it did nothing. When I told cpio to overwrite the files, it only did so for the files that it was told to. That is, only the files that the shell saw when it expanded /usr/jimmo/data/*. In other words, cpio did what it was told. I just told it to do something that I hadn't expected.

The solution is to find a way to pass the wild cards to cpio. That is, have the shell ignore the special significance of the asterisk. Fortunately there is away. By placing a backslash '\' before the metacharacter, you remove its special significance. This is referred to as "escaping" that character.

So, in my situation with cpio, when I referred to the files I wanted as /usr/jimmo/data/\*, the shell passed the arguments to cpio as /usr/jimmo/data/*. It was then cpio that expanded the * to mean all the files in that directory. Once I did that, I got the files I wanted.

Another interesting thing happens as a result of this wildcard expansion. The maximum number of characters that the shell can process on the command line is 5120. This seems like a lot (and it is), but some circumstances cause commands to run into this limit. SCO recognized this as a problem and increased the limit to 100,000 bytes by default.

Let's look at the directory /usr/include/sys. This contains files that are used by the system when making a copy of the kernel as well as by programmers. On my system, this directory contains over 200 files. If I change directories there and do a listing (ls), I see all the files and directories under /usr/include/sys. If I do an ls *, I also see a list of all the files and directories. All is as it should be.

Now, if we run ls /usr/include/sys. What do we get? What we expect: a list of all the files and directories in /usr/include/sys. If, however, we run ls /usr/include/sys/* we get:

sh: /bin/ls: arg list too long

What happened?

Well, it all has to do with the wild card expansion. Whenever we run ls with no arguments as with ls /usr/include/sys or changing directories and simply running ls, it is up to ls to scan the directory to give me the output. However, whenever we use wildcards, the shell must first process the command before it's handed off to ls. When we are in /usr/include/sys, and run ls *, there is no problem. Each of the files in /usr/include/sys is expanded as if we had typed in:

l Sdsk.h Srom.h ... vwdisp.h

All told, this works out to less than 2500 characters. (At least on my system).

On the other hand, if we run ls /usr/include/sys/* this is the same as if we had typed in:

l /usr/include/sys/Sdsk.h /usr/include/sys/Srom.h ... usr/include/sys/vwdisp.h

The total number of characters here is over 5700. This is well over the maximum number of characters per line. (Actually, it's the exec() system call that fails and not the shell itself. If that doesn't mean anything to you. Don't worry, it not important to understanding this limitation.)

Another aspect of this is what happens when you use this same example on a smaller directory. For example if we did a listing like this:

ls /etc/default

All we would see is the names of the files. If instead, we did it like this:

ls /etc/default/*

We see the directory names as well. This is as if we had done:

ls /etc/default/accounts /etc/default/authsh ...

In OpenServer, things are slightly different. The shell interacts with the command that is being executed. If the system finds that an asterisk is appropriate to pass to the command, it will do so and the asterisk is expanded by the command.

Another symbol that has special meaning is the dollar-sign ($). This is used as a marker to indicate that something is a variable. I mentioned earlier in this section that you could get access to your login name environment variable by typing:


The system stores your login name in the environment variable LOGNAME (note no '$'). The system needs some way of knowing that when you input this on the command line, you are talking about the variable LOGNAME and not the literal string LOGNAME. This is done through the '$'. There are several variables that are set by the system. You can also set variables yourself and use them later on. I get into more details about shell variables later on.

So far we have been talking about metacharacters used for searching the names of files. However, metacharacters can often be used in the arguments to certain commands. One example is the grep command, which is used to search for strings within files. The name grep comes from Global Regular Expression Print. As its name implies, it has something to do with regular expressions. Let's assume we have a text file called documents and we wish to see if the string "letter" exists in that text. The command might be:

grep letter documents

This will search for and print out every line containing the string "letter." This include such things as "letterbox," "lettercarrier," and even "love-letter." However, it will not find "Letterman," since we did not tell grep to ignore upper and lower case (using the -i option). To do so using regular expressions, the command mind look like this:

grep [Ll]etter documents

Now, since we specified to look for either "L" or "l" followed by "etter," we get both "letter" and "Letterman." We can also specify that we want to look for this strings only when it appears at the beginning of a line using the caret (^) symbol. For example:

grep ^[Ll]etter documents

This searches for all strings that start with the "beginning-of-line" followed by either "L" or "l" followed by "etter". Or, if we want to search for the same things at the end of the line, we would use the dollar-sign to indicate the end of the line. Note that the beginning of a strings the dollar-sign will be treated as the beginning of a string, whereas at the end of strings, it indicates the end of the line. Confused? Let's look at an example. Let's define a string like this:


If we echo that strings we simply get ^[Ll]etter. Note that this includes the caret at the beginning of the strings. When we do a search like this:

grep $VAR documents

It is equivalent to:

grep ^[Ll]etter documents

Now, if write the same command like this:

grep $VAR$ documents

This says to find to the strings defined by the VAR variable(^[Ll]etter) , but only if it is at the end of the line. Here were have an example, where the dollar-sign has both meanings. If we then take it one step further:

grep ^$VAR$ documents

This says to find the strings defined by the VAR variable, but only if it takes up the entry line. In other words, the line consists only of the beginning of the line (^), the string defined by VAR, and the end of the line ($).


One last issue that causes it's share of confusion is quotes. In SCO UNIX there are three kinds of quotes. They are referred to as double-quotes ("), single-quotes (') and back-quotes(`) (also called back-ticks). On most US keyboards, the single- and double-quotes are on the same key, with the double-quotes accessed by pressing shift and the single-quote key. Usually this is on the right side of the keyboard next to the enter key. The back-quote is usually in the upper left hand corner of the keyboard, next to the '1'.

To best understand the difference between the behavior of these quotes, I need to talk about them in reverse order. So we'll be talking first about the back-quotes or back-ticks.

When enclosed inside of back-ticks, the shell interprets that as meaning "the output of the command inside of the back-ticks." This is referred to as command substitution as the output of the command inside the back-ticks is substituted for the command itself. This is often used to assign the output of a command to a variable. As an example, let's say we wanted to keep track of how many files are in a directory. From the command line we could say:

ls | wc

The wc command gives me a word count, along with the number of lines and number of characters. Here, the command might come up as:

7 7 61

However, once the command is finished and the value has been output, we can only get it back again by re-running the command. Instead, If we said:

count=`ls |wc'

Then the entire line of output would be save in the variable count. If we then say echo $count, we get

7 7 61

Showing me that count now contains that line. If we wanted, we could even assign a multi-line output to this variable, we could use the ps command, like this:


Then type in:

echo $trash

Which gives me:

PID TTY TIME COMMAND 209 06 0:02 ksh 1362 06 0:00 ps

This is different from the output that ps would give when not assigned to the variable trash:













The next kind of quote, the single-quote ('), tells the system not to do any expansion at all. Let's take the example above, but this time turn the quotes around and use single quotes:

count='ls |wc'

If we were to now type echo $count, we get

ls |wc

What we got was exactly what we expected. The shell did no expansion and simply assigned the literal string "ls | wc" to the variable count. This even applies to the variable operator '$'. For example, if we simply say:

echo '$LOGNAME'

What comes out on the screen is:


No expansion is done at all and even the '$' is left unchanged.

The last set of quotes is the double-quote. This has partially the same effect as the single-quotes, but to a limited extend. If we include something inside of double-quotes, everything looses it's special meaning except for the variable operator ($), the back-slash (\), the back-tick (`) and the double-quote itself. Everything else takes on it's absolute meaning. For example, we could say:

echo "`date`"

which gives us:

Wed Feb 01 16:39:30 PST 1995

This is a round-about way of getting the date, but it is good for demonstration purposes. Plus, I often use this in shell scripts, when I want to log something. Remember that the back-tick first expands the command (by running it) and then the echo echoes it to the screen.

That pretty much wraps up the characters that have special meaning to the shell. You can get more details from the User's Guide if you need it, but the best way to see what's happening is to try a few combinations and see if they behave as you would expect.

A little while ago, I mentioned that some punctuation marks had special meaning. We already know about the special meaning of *, ?, and [ ]. What about the others? Well, in fact, most of the other punctuation marks have special meaning. I get into them in more detail when I talk about shell programming.

Pipes and Redirection

Perhaps the most commonly used character is |, which is referred to as the pipe symbol, or simply pipe. This enables you to pass the output of one command through the input of another. For example, say you would like to do a long directory listing of the /bin directory. If you simply type ls -l then press return, the names flash by much too fast for you to read. When it finally stops, all you see is the last 20 or so.

If instead we ran the command ls -l | more, we say that the output of the ls command is "piped through more". In this way we can scan through the list a screenful and a time.

Remember from our discussion of standard input and standard output in chapter 1? Standard input is just a file that usually points to your terminal. Standard output is also a file that usually points to your terminal in this case. The standard output of the ls command is changed to point to the pipe and the standard input of the more command is changed to point to the pipe as well.

The way this works is that when the shell sees the pipe symbol is it creates a temporary file on the hard disk. Although it does not have a name or directory entry, it does take up physical space on the hard disk. Since both the terminal and the pipe are seen as files from the perspective of the operating system, all we are saying is that the system should use different files instead of standard input and standard output.

Under SCO UNIX (as well as other UNIX dialects) there are the concepts of standard input, standard output and standard error. When you log in and are working from the command line, standard input is your terminal keyboard and both standard output and standard error are the terminal screen. In other words, the shell expects to be getting it's input from the keyboard and show the output (and any error messages on the terminal screen).

Actually, the three (standard input, standard output and standard error) are references to files that the shell automatically opens. Remember that in UNIX everything is treated as a file. When the shell starts, the three files it opens are usually the ones pointing to your terminal.

When we run a command like cat, it gets input from a file that it displays to the screen. Although it may appear that the standard input is coming from that file, the standard input (referred to as stdin) is still the keyboard. This is why when the file is large enough and more stops after each page, you can continue by pressing either the spacebar or enter key. That's because standard input is still the keyboard.

As it is running, more is displaying the contents of the file to the screen. That is, it is going to standard output (stdout). If you try to do a more on a file that does not exists, the message:

file_name: No such file or directory

shows up on your terminal screen as well. However, although it appears to be the same place, the error message was written to standard error (stderr). (We'll show this is different shortly)

One pair of characters that is used quite often, < and > also deal with stdin and stdout. The more common of the two, > redirects the output of a command into a file. That is, it changes standard output. An example of this would be ls /bin > myfile. If we were to run this command, we would have a file (in my current directory) named myfile that contained the output of the ls /bin command. This is because stdout is the file myfile and not the terminal. Once the command completes, stdout returns to being the terminal.

Figure 0-2 File redirection

Now, we want to see the contents of the file. we could simply say more myfile, but that wouldn't explain about redirection. Instead, we input more <myfile. This tells the more command to take it's standard input from the file myfile instead of the keyboard or some other file. (Remember even when stdin is the keyboard, it is still seen as a file.)

What about errors? As I mentioned, stderr appears to be going to the same place as stdout. A quick way of showing that it doesn't is by using output redirection and forcing an error. If wanted to list two directories and have the output go to a file, we run this command:

ls /bin /jimmo > /tmp/junk

we get a message:

/jimmo not found

However, if we look in /tmp, there is, indeed, a file called junk which contains the output of the ls /bin portion of the command. What happened here was that we redirected stdout into the file /tmp/junk. Which it did with the listing of /bin. However, since there is no directory /jimmo (at least not on my system), we got the error /jimmo not found. In other words, stdout went into the file, but stderr still went to the screen.

If we want to get both the output and any error messages to go to the same place, we can do that. Using the same example with ls the command would be:

ls /bin /jimmo > /tmp/junk 2>&1

The new part of the command is 2>&1. This says that file descriptor 2 (stderr) should go to the same place as file descriptor 1 (stdout). By changing the command slightly:

ls /bin /jimmo > /tmp/junk 2>/tmp/errors

We can tell the shell to send any errors some place else. You will find it quite often in shell scripts thoughout the system that the file that error messages are send to /dev/null. This has the effect of ingoring the messages completely. They are neither displayed on the screen nor sent to a file.

Note that this command does not work as you think:

ls /bin /jimmo 2>&1 > /tmp/junk

The reason is that we redirect stderr to the same place as stdout before we redirect stdout. So, stderr goes to the screen, but stdout goes to the file specified.

Redirection can also be combined with pipes like this:

sort < names | head


ps | grep sh > ps.save

In the first example, the standard input of the sort command is redirected to point to the file names. It's output is then passed to the pipe. The standard input of the head command (which takes the first 10 lines) also comes from the pipe. This would be the same as the command:

sort names | head

In the second example, the ps command (process status) is piped through grep and the output of the whole thing is redirected to the file file.save.

Figure 0-3 Data flow through a pipe

If we want to redirect stderr, we can. The syntax is similar, but it differs slightly from shell to shell. Therefore, I am going to wait until I talk about the individual shells.

It's possible to input multiple commands on the same command line. This can be accomplished by using a semi-colon (;) between commands. I have used this on occasion to create command lines like this:

man ksh | col -b > man.tmp; vi man.tmp; rm man.tmp

This command redirects the output of the man-page for ksh into the file man.tmp. (The pipe through col -b is necessary because of the way the man-pages are formatted). Next, we are brought into the vi editor with the file man.tmp. After I exit vi, the command continues and removes my temporary file man.tmp. (After about the third time of doing this, it got pretty monotonous, so I create a shell-script that did this for me. I'll talk more about shell-scripts later.)

Interpreting the Command

One question that I had was "in what order does everything gets done?" We have shell variables to expand, aliases and functions to process, "real" commands, pipes and input/output redirection. There are a lot of things that the shell has to consider when figuring out what to do and when.

For the most part this is not so important. Commands do not get so complex that knowing the evaluation order becomes an issue. However, on a few occasions I have run into situations were things did not behave as I thought they should. By evaluating the command myself (as the shell would) it became clear what was happening.

Let's take a look.

The first thing that gets done is for the shell to figure out how many commands there are on the line. (Remember you can separate multiple commands on a single line with semi-colon.) This process determines how many tokens there are on the command line. In this context a token could be an entire command or it could be a control word such as 'if'. Here, too, the shell has to deal with input/output redirection and pipes.

Once it determines how many tokens it checks the syntax of each of the tokens. Should there be a syntax error, the shell will not try to start any of the commands. If the syntax is correct, it begins interpreting the tokens.

The first thing it checks for is aliases. Aliases are a way for some shells to allow you to define your own commands. If any of the tokens on the command line is actually an alias that you have defined, it is expanded before the shell proceeds. If it happens that an alias contains another alias, they are expanded before continuing with the next step. Here, functions are expanded. Like functions in programming languages like C, a shell function can be thought of a small sub-program. We'll get into both aliases and functions shortly.

Once aliases and functions have all been completely expanded, the shell evaluates variables. Finally, it uses any wildcards to expand them to file names. This is done according to the rules we talked about previously.

After it has evaluated everything, it is still not ready to run the command. It first checks to see if the first token represents a command built into the shell or an external one. If it's not external, the shell needs to go through the search path.

At this point, it sets up the redirection, including the pipes. These obviously have to be ready before the command starts since the command may be getting it's input from some place other than the keyboard and may be sending it somewhere other than the screen. Figure 0-4 shows how the evaluation looks graphically.

Note: This is an oversimplification. Things do happen in this order, however there are many more things that occur in and around the steps I have listed here. What I am attempting to describe is the general process that occur when the shell is trying to interpret your command.

Once the shell has determined what each command is and has determined that it is an executable binary program (not a shell script) the shell makes a copy of itself using the fork() system call. This copy is a "child" process of the shell. It then uses the exec() system call to overwrite itself with the binary it wants to execute. (We talked more about the fork-exec pair earlier). Keep in mind that even though the child process is executing, the original shell is still in memory, waiting for the child to complete (Assuming the command was not started in the background with &)

If the program that needs to be executed is a shell script, the program that is fork-exec'ed() is another shell. This new shell starts reading the shell script and interprets it, one line at a time. This is why a syntax error in a shell script is not discovered when the script is started, but rather when the erroneous line is first encountered.

Understanding that a new process is created when you run a shell script helps to explain a very common misconception under UNIX. When you run a shell script and that script changes directories, your original shell knows nothing about that change. This confuses a lot of people who are new to UNIX as they come from the DOS world where changing the directory from within a batch file does change it. This is because DOS does not have the same concept of a process as UNIX does.

Look at it this way: The sub-shell's environment has been changed in that the current directory is different, but this is not passed back to the parent. Like "real" parent-child relationships, only the children can inherit characteristics from their parent, not the other way around. Therefore, any changes to the environment, including directory changes are not noticed by the parent. Again, this is different from the behavior of DOS .bat files.

You can get around this by either using aliases or shell functions (assuming that your shell has them). Another way is using the dot (.) command in front of the shell script you want to execute. For example:

. myscript ¬NOTICE THE DOT

This script will then be interpreted directly by the current shell, without forking a sub-shell. If the script makes changes to the environment, then it is this shell's environment that is changed.

You can use this same behavior if ever you need to reset your environment. Normally, your environment is defined by the startup files in your home directory. On occasion, things get a little confused (maybe a variable got changed or removed) and you need to reset things. By using the . (dot) command you can reset your environment. For example, with either sh or ksh you can do it like this:

. $HOME/.profile

Or, using a function of ksh you can also do

. ~/.profile

This uses the tilde (~), which I haven't mentioned, yet. Under the ksh, this is a shortcut way of referring to a particular user's home directory. I'll talk more about this in the section on ksh.

If you have csh, the command is issued like this:

source $HOME/.login

The Different Kinds of Shells

The great-grandfather of all shell is /bin/sh, called simply sh or the Bourne-Shell. (Named after it's developer Steven Bourne) This is the 'standard' shell and the one that you will find on every version on UNIX (At least all the ones I have seen). Although many changes have been made to UNIX, sh has remained basically unchanged.

All the capabilities of 'the shell' I've talked about so far apply to sh. Anything I've talked about that sh can do, the others can do as well. So rather than going on about what sh can do (which I already did), I am going to talk about the characteristics of the other two shells, csh and ksh.

SCO also includes both a visual/menu driven shell, 'scosh', as well as "restricted" versions of the other shells. I think of scosh as more of an application than a shell, like sh, ksh or csh. Therefore, we will therefore need to leave it for another time and place. The restricted versions of each shell all have the same basics characteristic of their non-restricted counterpart. I get into this a little in chapter 4. In addition, there are many different shells that are either available as public domain, shareware, or commercial products that can be installed on SCO UNIX. However, since they are not provided with the base system, we will need to skip them.

Figure 0-4 Evaluating a command

In the section on shell basics, we talked about environment variables. As I mentioned these are set up for you as you are logging in or you can set them up later. Depending on what shell you use, the files used and where they are located is different. Some variables are made available to everyone on the system and are accessed through a common file. Others reside in the user's home directory. Normally, those residing in the user's home directory can be modified. If you can't, then maybe the system administrator has a reason or there is some other problem. Talk to them about it.

One convention I will be using here is how I refer to the different shells. Often I will say "the csh" to refer to the C-Shell as a concept and not the program /bin/csh or to "the sh" to refer to the "Bourne Shell" as an abstract entity and not specically to the program /bin/sh.

Most of the issues I am going to address here are detailed in the appropriate man-pages and other docs. Why cover them here? Well, in keeping with the basic premise of this book, I want to show you the relationships involved. In addition, many of the things I am going to talk about are not emphasized as much as they should. Often users will go for months or years without learning the magic that these shells can do. Since they "looked at" the manuals but never read them cover to cover, they missed some of these things. Things I feel are too exciting to miss out on.

There is one oddity that really needs addressing. This is behavior of the different shells when moving through symbolic links. As I mentioned before, symbolic links are simply pointers to files or directories elsewhere on the system. If you change directories into symbolic link, then your "location" on the disk is different than what you might think. In some cases, the shell understands the distinction and hides from you the fact that you are somewhere else. This is where the problem lies.

Although the concept of symbolic links exists in ODT, it was not as wide spread as in OpenServer. OpenServer uses the symbolic link as a key administrative tool. The directories I will be talking about here as symbolic links only exist in OpenServer. Let's take the directory /usr/adm as an example. Since it contains a lot of administrative information it is a useful and commonly accessed directory. This is actually a symbolic link to /var/adm. If we are using sh as our shell, when we do a cd /usr/adm and then pwd, the system responds with: /var/adm. This is where we are "physically" located, despite the fact we did a cd to /usr/adm. If we do cd .. (to move up to our parent directory) we are now located in /var. All this seems logical. This is also the behavior of csh.

However, if we use ksh things are different. This time when we do a cd /usr/adm and then pwd, the system responds with: /usr/adm. This is where we are "logically". If we now do a cd .., we are located in /usr. Which of these is the "correct" behavior? Well, I would say both. There is nothing to define what the "correct" behavior is. Depending on your personal preference, either is correct. I tend to prefer the behavior of ksh since I wanted to be in /usr/adm and not /var/adm. However, the behavior of sh and csh is also valid.

The C-Shell

One of the first "new" shells to come around was the csh or 'C-Shell'. It is so name because much of the syntax it uses is very similar to the C programming language. This isn't to say that this shell is only for C programmers, or programmers in general. Rather knowing C makes learning the syntax much easier. However, it isn't essential. (Note: the csh syntax is similar, so don't get your dandruff up if it's not exactly the same)

The csh is normally the shell that users get on UNIX systems. Every place I ever got a UNIX account, it was automatically assumed that I wanted csh as my shell. When I first started out with UNIX, that was true. In fact, this is true for most users. Since they don't know any other shells, the csh is a good place to start.

As you login with csh as your shell, the system first looks in the global file /etc/cshrc. Here the system administrator can define variables or actions that should be taken by every csh user. Next, the system reads two files in your home directory: .login and .cshrc. The .login file normally contains the variables you want set and the actions you want to occur each time you log in. This can include setting you terminal type or warning you that your password is about to expire.

In both of these files, settings variables has a syntax unique to the csh. This is one major difference between the csh and the other two shells and why it is not a good idea to give root csh as it's default shell. The syntax for csh is:

set variable_name=value

Whereas for the other two it is simply:


Once the system has processed your .login file, your .cshrc is processed. The .cshrc contains things that you want executed or configured every time you start a csh. At first, I wasn't clear with this concept. If you are logging in with the csh, don't you want to start a csh? Well, yes. However, the reverse is not true. Every time I start a csh I don't want the system to behave as if I were logging in.

Let's take a look as this for a minute. One of the variables that gets set for you is the SHELL variable. This is the shell you use any time you do a shell escape from a program. What a "shell escape" is starting a shell as a sub-process of that program. Remember in our discussion of operating system basics we talked about the concept of processes. As a rather contrived example, I described a case where you would start vi, then "jump out". This "jumping out" is called a shell escape as you "escape" from your current program to get to a shell.

When you do a shell escape, the system starts a shell as a new (child) process of whatever program you are running at the time. As we talked about earlier, once this shell exits, you are back to the original program. Since there is no default, the variable must be set to a shell. If set to something else, you end up with an error message like vi gives you:

invalid SHELL value: <something_else>

Where <something_else> is whatever your SHELL variable is defined as.

If you are running csh and your SHELL variable is set to /bin/csh, every time you do a "shell escape" the shell you get is csh. If you have a .cshrc file in your home directory, not only is this started when you log in, but anytime you start a new csh. This can be useful if you want access personal alias from inside of sub-shells.

What are "personal aliases"? No, this isn't the ability to call yourself Thaddeus Jones when your real name is Jedediah Curry. However, it is the ability to use a different name for a command. In principle, they can be anything you want. They are special names that you define to accomplish tasks. These aren't shell scripts, as a shell script is external to your shell. To start up a shell script, you type in it's name, the system starts a shell as a child process of your current shell in order to run the script.

Aliases, too, are started by typing them in. However, they are internal to the csh. That is, they are internal to your csh process. Instead of starting a sub-shell, the csh executes the alias internally. This has the obvious advantage of being quicker as there is no overhead of starting the new shell or searching the hard disk.

Another major advantage is the ability to create new commands. Granted you can do that will shell scripts (which we get into later), but the overhead of creating a new process does not make it worth while for simple things. Aliases can be created with multiple commands strung together. For example, I created an alias 't'. That shows me the time. Although the date command does that, all I want to see is the time. So, I created an alias 't', which I defined like this:

alias t=`date | cut -c12-16'

When I type in 't', I get the hours and minutes, just exactly the away I want it.

Aliases can be defined in either the .login or the .cshrc. However, as I described above, if you want them for all sub-shells, they need to go in .cshrc. If you are running a Bourne-Shell, aliasing may be the first good reason to switch to another shell.

However, the sh does have the means of creating new commands. This is done by creating shell functions. Shell functions are just like those in a programming language. Set of commands are grouped together and jointly called by a single name. The csh can create them as well.

The format for functions is the same for all three shells:



first thing to do

second thing to do

third thing to do


Functions can be defined anywhere, this include from the command line. All you need to do is simply type in the lines one at a time, similar to the way shown above. The thing to bear in mind is that if you type it from the command line, once you exit that shell, the function is gone.

Be careful when creating aliases or functions so that you don't redefine existing commands. Either you end up forgetting the alias or some other program uses the original program and fails since the alias gets called first. I once had a call where the customer had a system where he could no longer install software. The custom program was failing. So, we tried replacing the custom program, but that didn't work. We tried replacing the files in /etc/perms, but that didn't work. Since it was an SCO product he was trying to install, I assumed that it was defective media. Fortunately, he had another copy of that same product, but custom died with the same error. It didn't seem likely that it was bad media, too. At this point, I have been with him for almost an hour, so I decided to hand it off to someone else. (Often a fresh perspective is all that is needed)

About an hour later one of the other engineers comes into my cubie with the same problem. He couldn't come up with anything either. (Which kind of relieved me) So, he decided that he needed to research the issue. Well, he found the exact message in the custom source code and it turns out that this message comes when custom cannot run the sort command. Ah, a corrupt sort binary. Nope! Not that easy. What else was there. As it turns out, the customer had created an alias called sort we he used to sort directories in a particular fashion. Since custom couldn't work with this version of sort, it died.

So, why use one over the other? Well, if there is something that can be done with a short shell script, then it can be done with a function. However, there are things that are difficult to do with an alias. One thing is making long, relatively complicated commands. Although you can do this with an alias, it is much simpler and easier to read if you do it with a function. I will go into some more details about shell functions later in the section on shell scripting. You can also find out more details in the csh man-page.

I mentioned that aliases are a good reason to switch from sh to csh. However, that's not the only reason. Another advantage that the csh has is its ability to repeat, and even edit, previous commands. Commands are stored in a shell "history list", which, be default contains the last 20 commands. This is normally defined in your .cshrc file or you can do so from the command line. The command set history=100 would change the size of your history list to 100. One thing to keep in mind is that everything you type at the command line is save in the history file. Even if you mis-typed something, the shell tosses it into the history file.

What good is the history file? Well, the first thing is that by simply typing 'history' with nothing else you get to see the contents of your history file. That way if you can't remember the exact syntax of a command you typed in five minutes ago, you can check your history file.

This is a nice trick, but it goes far beyond that. Each time you issue a command from the csh prompt, the system increments an internal counter that tells the how many commands have be issued up to that point. If you have a default csh, then your prompt is probably a number followed by a '%, that number is the current command. You can use that number to repeat those previous commands. This is simply done with an exclamation mark (!) followed by the command number as it appears in the shell history.

For example, if the last part of your shell history looked like this:

21 date

22 vi letter.john

23 ps

24 who

You could edit the letter again by simply typing in !22. This repeats the command vi letter.john and adds this command to your history. After you get done editing the file, this portion of the history file would look like:

21 date

22 vi letter.john

23 ps

24 who

25 vi letter.john

Another neat trick that's built in to this history mechanism is the ability to repeat the commands without using the numbers. If you know that sometime within that history you edited a file using vi, you could edit again by simply typing !vi. This search backwards though the history file until it finds the last time you used vi. If there were no other commands since the last time you used vi you could also start it with !v.

If you want to redo the command you just entered, you could do so simply by typing in !!.

This history mechanism can also be used to edit previously issued commands. Let's say that instead of typing vi letter.john, we had typed in vi letter.jonh. Maybe we know someone named jonh, but that's not who we meant this letter to be addressed to. So rather than typing in the whole command, we can edit it. The command we would issue would be !!:s/nh/hn/.

At first, this seems a little confusing. The first part, however, should be clear. The "!!' told the system to repeat the previous command. The colon (:), tells the shell to expect some editing commands. The s/nh/hn/ says to substitute for pattern nh the hn. (If you are familiar with vi or sed you understand this. If not, we get into this syntax later.)

What happens if we had edited a letter to john, done some other work and decided we wanted to edit a letter to chris instead. we could "simply" type !22:s/john/chris/. Granted that is actually more key strokes than if we had typed everything over again. However, you hopefully see the potential for this. Check out the csh man-page for many different tricks for editing previous commands.

In the default .cshrc are two aliases that I found quite useful. These are pushd and popd. These aliases are used to maintain a directory "stack". When you run pushd <dir_name> your current directory is pushed onto (added to) the stack and you change directory to <dir_name>. When you use popd it pops (removes) the top of the directory stack and you change directories to it.

Like other kinds of stacks, this directory stack can be several layers deep. For example, let's say that we are currently in our home directory. A 'pushd /bin' makes our current directory /bin with our home directory the top of the stack. A 'pushd /etc' and now we are in /etc. we do it one more time with pushd /usr/bin and now we are in /usr/bin. The directory /usr/bin is now the top of the stack.

If we run popd (no argument), then /usr/bin is popped from the stack and /etc is our new directory. Another popd and /bin is popped and we are now in /bin. One more pop brings me back to our home directory. (In all honesty, I have never used this to do anything more than to switch directories, then jump back where I was. Even that is a neat trick.)

Figure 0-5 Changes in a directory stack

There is another neat trick built into the csh for changing directories that is very useful. This is the concept of a directory path. Like the execution search path, the directory path is a set of values that are searched for matches. Rather than searching for commands to execute, the directory path is searched for directories to change into.

The way this works is by setting the cdpath variable. This is done like any other variable in csh. For example, if, as system administrator, we wanted to check up on the various spool directories, we could define cdpath like this:

set cdpath = /usr/spool

Then if we entered

cd lp

If the shell can't find a sub-directory of current one named lp it looks in the cdpath variable. Since it is defined as /usr/spool and there is a /usr/spool/lp directory we jump into /usr/spool/lp. From there, if we type:

cd mail

We jump to /usr/spool/mail. we can also set this to be sever directories like this:

set cdpath = ( /usr/spool /usr/lib /etc )

In doing so, each of the three named directories will be searched.

The csh also can make guesses about where you might want to change directories. This is accomplished through the cdspell variable. This is Boolean variable (true/false) that is set simply by typing:

set cdspell

When set, the cdspell variable tells the csh that it should try to guess what is really meant when we misspell a directory name. For example, if we typed

cd /sur/bin (instead of /usr/bin)

The cdspell mechanism will attempt to figured out what the correct spelling should be. You are then prompted with the name that it guessed as being correct. By typing in anything other than 'n' or 'N' you are changed into this directory. There are limitations, however. Once it finds what it thinks is a match it doesn't search any further.

For example. we have three directories 'a', 'b' and 'c'. If we type 'cd d', all three could be the one we want. The shell will make a guess and choose one, which may or may not be correct.

The Korn Shell

When I first started at SCO, I was given a csh and once I figured out all it could do, I enjoyed using it. I found the editing to be cumbersome from time to time, but it was better than retyping everything.

One of my co-workers, Kamal, was an avid proponent of the Korn-Shell. Every time he wanted to show me something on my terminal he would grumble when he forgot that I wasn't using ksh. Many times he tried to convert me, but learning a new shell wasn't high on my list of priorities.

I often complained to Kamal how cumbersome vi was (at least I though so). One day I asked him for some pointers on vi, since every time I saw him do something in vi it looked like magic. He agreed with one condition, that at least I try the ksh. All he wanted to do was to show me one thing and if after that I still wanted to use the csh, that was my own thing. Not that he would stop grumbling, just that it was my own thing.

The one thing that Kamal showed me convinced me of the errors of my ways. Within a week I had requested the system administrator to change my login shell to ksh.

What was that thing? Well, from what I told you above, you know that the csh allows you to edit previous commands. What if there is a way to edit the commands as if you were in vi? As it turns out, there is. Once Kamal showed me how to do it, I felt like the csh editing mechanism is like using a sledge-hammer to pound in a nail. It does what you want, but it is more work than you need.

Like the csh, the ksh has a history mechanism. The ksh history mechanism has two major advantages over that of the csh. First, the information is actually saved to file. This is either defined by the HISTFILE environment variable before the shell is invoked or it defaults to .sh_history in your home directory. At any point you can edit this file and make changes to what the ksh perceives as your command history.

This could be useful if you knew you were going to be issuing the same commands every time you logged in and you didn't want to create aliases or functions. If you copied a saved version of this file (or any other text file) and named it .sh_history then you would immediately have access to this new history. (Rewriting history? I shudder at the ramifications)

The next nice trick is the ability to edit directly from the commands line any of the lines in you .sh_history file. If you have your EDITOR environment variable set to vi or you use the set -o vi command, you can edit previous commands using many of the standard vi editing commands.

To enter edit mode you press ESC. You can now scroll through the lines of your history file using the movement keys from vi (h-j-k-l). Once you have found the line you are looking for you can use other vi commands to delete, add, change, whatever. If you press 'v', you are brought into the full-screen version of vi. (This I found out by accident) For more details check out the vi or ksh man-page or the section later on vi.

In the section above on csh, I talked about using pushd and popd to keep track of what directories you are in. Unfortunately, the ksh cannot maintain a directory stack. However, it does keep track of your last directory in the OLDPWD environment variable. Whenever you change directories, the system saves your current directory in OLDPWD before it changes you to the new location.

You can use this by simply entering cd $OLDPWD. Since the variable $OLDPWD is expanded before the cd command is executed, you end up back in your previous directory. Although this is more characters that just popd, it's easier since the system keeps track of this for me. Also since it's a variable I can access it in any way I can other environment variables.

For example, if there was a file in our old directory that we wanted to move to our current one, we could do this by entering:

cp $OLDPWD/<file_name> ./

However, things are not as hard as they seem. Typing in cd $OLDPWD is still a bit cumbersome. It is a lot less characters to type in popd like in the csh. Why isn't there something like that in the ksh. There is. In fact, it's much simpler. When I first found out about it, the adjective that first came to mind was "sweet". To change directories to your previous directory, simply type in 'cd -'.

Another pre-defined "variable" is the tilde (~), which is a shortcut way of referring to a particular user's home directory. By itself, it refers to the home directory of the user using it. If you follow it with a user's name, then it refers to the home directory of that user. For example, ~jimmo refers to the home directory of the user jimmo.

Next: Commonly Used Utilities

Next Chapter: Users and User Accounts


Copyright 1996-1998 by James Mohr. All rights reserved. Used by permission of the author.

Be sure to visit Jim's great Linux Tutorial web site at https://www.linux-tutorial.info/