Sams Teach Yourself Shell Programming in 24 Hours
(Publisher: Macmillan Computer Publishing)
Author(s): Sriranga Veeraraghavan
ISBN: 0672314819
Publication Date: 01/01/99

Previous Table of Contents Next


Tailoring Your Path

At many companies and schools, a user’s home directory is accessible from many machines running different versions of UNIX. A problem that I face every day is using my shell initialization script, .profile, on Linux, Solaris, FreeBSD, and HP-UX machines.

Although many issues arise involving cross-platform initialization scripts, one of the largest issues is getting your PATH variable set correctly. Because each different UNIX platform stores commands in different directories, your initialization script must be able to tailor the value of the variable PATH. This problem has four possible solutions:

  Maintain UNIX version-specific initialization files.
  Maintain UNIX version-specific sections in your initialization files.
  Use the same PATH on all versions of UNIX, by having it include all possible directories of interest on all versions. This solution relies on the fact that the shell ignores directories in the path that do not exist.
  Set a different PATH depending on the existence of individual directories.

The first two solutions are difficult to maintain. Each change or new UNIX version that you have to work with means that you have to create either a new initialization file or a new section on your initialization file. The complexity of your shell initialization process increases drastically with either of these approaches.

The third option is easy to implement and maintain, but it results in a PATH that is extremely long and hard to restructure. If you have more than one or two machines, PATH can grow to contain dozens of entries.

The fourth option is the easiest to maintain and extend, so how do you implement it?

The simplest way is to have a for loop that checks each directory and includes it if it exists:

PATH=
for DIR in /bin /sbin /usr/bin /usr/sbin /usr/ccs/bin /usr/ucb ;
do
    if [ -d "$DIR" ] ; PATH="$PATH:$DIR" ; fi
done
export PATH

At this point you might wonder why I am discussing this problem in this chapter and not in a previous chapter. The reason is that the complete problem is more than simply tailoring PATH on a per-UNIX-version basis. The complete problem requires you to tailor PATH for both interactive shells and for different user IDs.

You can solve these problems by including several case statements with different for loops in them, or you can write one function and reuse it. Because this chapter covers functions, I will show you how to do the latter.

The function that you need is quite simple. Rewrite the for loop to use the functions’ arguments rather than a list of directories:

SetPath() {
    for _DIR in "$@"
    do
        if [ -d "$_DIR" ] ; then PATH="$PATH":"$_DIR" ; fi
    done
    export PATH
    unset _DIR
}

This function has three important points:

  This function exports the PATH variable before it finishes. This is required to ensure that the child process started by the shell can access the value of the variable.
  This function unsets its internal variable _DIR. It is a good habit to unset all variables that should not be exported. In some languages, this is not an issue because local variables disappear after execution leaves the scope of the function. In other languages, variables can be marked for local scope only, but in shell all variables are of global scope. This means that the programmer must take care in managing variables.
  The local variable, _DIR, starts with the underscore _ character. You do this to avoid name conflicts with variables set by other functions. It is not required, but it is good practice to name your local function variables differently than global variables.

There is only one additional thing to add to this function, and that is to check to see whether PATH is set. Without this check, you can potentially end up with a PATH set to

PATH=:/usr/bin:/usr/sbin:/usr/local/bin

The problem here is that the first entry in the variable PATH appears to be a null string. Some versions of the shell cannot deal with this entry, so you need to prevent this from happening.

You can do this check using variable substitution. The complete function is

SetPath() {
    PATH=${PATH:="/sbin:/bin"};
    for _DIR in "$@"
    do
        if [ -d "$DIR" ] ; then PATH="$PATH":"$DIR" ; fi
    done
    export PATH
    unset _DIR
}

Here you set PATH to /sbin:/bin if it is unset. You can invoke this function as follows:

SetPath /sbin /usr/sbin /bin /usr/bin /usr/ccs/bin

It checks to see whether each of its arguments is a directory, and if a directory exists, it is added to PATH.

Sharing Data Between Functions, an Example

The functions seen thus far operate independently of one another, but in most shell scripts functions either depend on or share data with other functions.

In this section you will look at an example where three functions work together and share data.

Moving Around the File System

The C shell, csh, provides three commands for quickly moving around in the UNIX file system:

  popd
  pushd
  dirs

These commands maintain a stack of directories internally and enable the user to add and remove directories from the stack and list the contents of the stack.

For those readers who are not familiar with the programming concept of a stack, you can think of it as a stack of plates: you can add or remove a plate only at the top of the stack. You can access only the top plate, not any of the middle plates in the stack. A stack in programming terms is similar. You can add or remove an item only at the top of the stack.

In csh, the stack is maintained within the shell, but in your shell function–based implementation you have to maintain the stack as an exported environment variable so that all three functions have access to it.

Use the variable _DIR_STACK to store the directory stack. Each entry in the stack is separated by the : character similar to the PATH variable. By using this character rather than a space or tab, you increase the flexibility of the directory names that you can handle.


Previous Table of Contents Next