Features


Tools For MS-DOS Directory Navigation

Leor Zolman


Leor Zolman wrote "BDS C", the first C compiler designed exclusively for personal computers. Since then he has designed and taught programming workshops and has also been involved in personal growth workshops as both participant and staff member. He STILL doesn't hold any degrees. His latest incarnation is as a CUJ staff member.

As an MS-DOS user with a large amount of hard disk space to manage, I frequently find myself cd-ing all over the system in pursuit of source files and data. The standard MS-DOS command processor COMMAND.COM's repertoire of options for facilitating system navigation is bare-bones and full of idiosyncrasies. For instance, to change directly to an arbitrary drive and user area, the user must enter the drive selector and path specification as two separate commands. Switching from the root directory of drive C: to the \work directory on drive D: requires the command sequence:

C:\>d:            (select D:)
D:\>cd work       (change to the desired directory)
D:\WORK>...
(All examples assume the PROMPT environment variable is set to $p$g so that COMMAND.COM will display the current path as part of the system prompt.)

If the user attempts to select a different drive and a path with one command, he will find that apparently nothing has happened:

C:\>cd d:\work
C:\>...
Actually, the system has selected the specified path to be active on the specified drive, but the specified drive is not selected to be current! The system maintains a current working directory for each logical drive. If the user were then to select that other drive, i.e.,

C:\>d:
D:\WORK>...
then the selected path would show up as the current directory.

Another "missing" feature in the standard command environment is a simple directory-name aliasing mechanism, so that one can switch quickly to commonly-used directories even if the path name happens to be lengthy. MS-DOS does provide a simplistic facility (the subst command) to relate an arbitrary path to a new drive designator, but subst isn't really adequate: the alias name is limited to a single letter and there is no facility for viewing all active assignments. I would prefer to have the ability to assign arbitrary mnemonics to arbitrary paths, and to have those mnemonics be recognized when specified in cd commands.

I would also like some clean mechanism for instantly switching to the previous directory — even if I've forgotten what it was.

The Answer

To address these needs, I wrote an extended CD command that supports combined drive and path specifications and a companion command that returns the user to the previous directory (taking the directory specification from information recorded in an environment variable by the extended cd command). The cd-replacement stores the old full path name in an environment variable before switching to a new specified path, and the companion command reads this environment variable and returns to the original directory upon request. Since the extended cd must modify its parent's environment, it uses the functions for modifying the master environment which appeared in the July 1989 issue of CUJ.

CDE (for CD Extended) works similarly to MS-DOS's cd command, except for the following special cases:

In support of the "return to previous directory" feature, I decided to implement a "directory stack" mechanism. This stack is maintained via environment variables, and the user may select the naming convention for those variables by customizing the #define statements in CDERET.H. (See
Listing 1. )

One master environment variable (I call it CHAINS) specifies the maximum size of the directory stack. When CDE is first invoked, CDE checks to see if the CHAINS variable has been previously defined in the environment; if so, its current value is used. If not, CDE initializes CHAINS to a default value (also specified by a definition in the header file). Thus, the user has the option of setting the value of CHAINS explicitly (using the standard built-in command set) or allowing CDE to handle the initialization of CHAINS automatically. (See Listing 2. )

A "stack" of size CHAINS is represented by a set of environment variables named by a common base name (I use CHAIN) with position numbers appended. Thus, with CHAINS=3, after several CDEs the environment variables CHAIN1, CHAIN2 and CHAIN3 would be created to store the pertinent path names in the environment.

Every time CDE is used to change directories, it "pushes" the old current working directory "on the stack" by reassigning all the relevant environment variables. CHAIN1 is always the top of the stack, CHAINn (where n = CHAINS) is the base. Since there is no disk activity involved, this process is quite fast.

The RET command (Listing 3) "returns" to the previous directory (either specified by CHAIN1 or undefined), then "pops" the stack by reassigning all the active environment variables in reverse order.

As long as CHAINS is greater than 1, then the directory stack behaves as described above and successive uses of RET unravel the stack.

When CHAINS is set to 1, RET considers this a special case: after returning to the directory specified by CHAIN1, CHAIN1 is reset to the name of the directory that was current at the time of the RET call. Thus, repeated uses of RET with CHAINS equal to 1 effect a "toggle" between two directories. Depending on the way your system is organized, this toggling mechanism may be more useful to you than the directory stack mechanism.

Icing

The directory aliasing feature is activated by simply setting an environment variable to the full path desired, then using that environment variable name as a parameter to CDE. For example,

C:\>set WORK=d:\project\subproj\
                new\testing
C:\>cde work
D:\PROJECT\SUBPROJ\NEW\TESTING>...
As a special case, for convenience, giving the CDE command without any arguments will cause CDE to look for a special environment variable (I call it HOME) and switch to the directory it specifies. If you spend much of your time headquartered at one particular directory, this is an easy way to go back to it from anywhere in the system, regardless of the state of the directory stack. The current directory at the time this special form of CDE is given will, as usual, be recorded in the environment by CDE in case you want to use RET from the HOME directory.

When setting environment variables in general, be careful not to type spaces between the end of the variable name and the = sign. DOS would keep the space as part of the variable name, and things wouldn't work. The CDE program will handle spaces after the = sign (and before the text) with no problem, but it's probably safer to be consistent and use no spaces whatsoever.

Implementation

Both CDE.C and RET.C have two phases of operation: the first phase performs the required drive/directory selection, and the second phase updates the related environment variables. If the first phase fails, then the programs exit immediately; there's no need to update environment variables if the current directory wasn't changed.

To obtain the name of the target directory in phase one, RET simply accesses the CHAIN1 environment variable. If the variable does not exist, then CDE has never been run and an appropriate message is displayed. If CHAIN1 exists, it specifies the target path. CDE.C gets its target path name from the command line. If the name happens to be the name of an active environment variable, then the value of the variable with that name is used to obtain the target path.

The directory selection process itself is identical for both commands and takes two steps: the selection of the logical drive and the selection of the desired directory. The drive is selected first; if that fails, we quit and no harm has been done. Once the new drive has been selected, then the new path is selected. If that fails, we have to go back and reinstate the original drive. If it succeeds, we're done with phase one.

Phase two for RET.C is relatively straightforward. If CHAINS is equal to 1, then the CHAIN1 environment variable is set to the original current directory name (before phase one) in order to support the toggling feature. For other values of CHAINS, the directory stack is "popped" by looping to reassign each CHAINn variable to the value of its next higher counterpart.

CDE's phase two begins by making sure the CHAINS environment variable, used to specify the stack size, is present and initialized. If it exists, its value is assigned to the program variable chaincnt. If CHAINS does not exist, then it is initialized to the default value (specified by the symbolic string constant DEFAULT_CHAINS).

Finally the directory stack is "pushed" by copying each CHAINn variable (for n = 1 to CHAINS-1) to its next higher counterpart. CHAIN1 is a special case; it is assigned to the name of the current directory before phase one was completed.

Configuration

The following symbolic constants may be changed to suit your own preferences:

CHAINS_VAR The master directory chain size control variable

CHAIN_BASE The "base" name of directory stack variables

DEFAULT_CHAINS The default value for CHAINS_VAR (in quotes)

HOME_NAME The name of the env. variable for home directory

The CDE.EXE and RET.EXE commands should be placed in a directory that is somewhere in your system search path. (I use c:\bin for all my personal utilities.)

System-Dependent Functions

The two areas of high compiler-dependency in this application, direct console I/O and DOS logical drive selection, have been isolated in a separate utility library named UTIL.C (Listing 4) . The only support function required by the functions in UTIL.C is the bdos function typically supplied with most popular compiler libraries.

If you need to write the bdos function yourself, the prototype is shown at the top of the UTIL.C source file. It takes an interrupt (int 21h) function number, a DX register value, and an AL register value as parameters (although the AL parameter is not needed for this application). The bdos function can easily be written in terms of any of the more general operating system interface functions (int86(), intdos(), etc) you may have available.

To keep the commands' .EXE file sizes as short as possible, all messages are displayed on the console using direct console I/O calls (through bdos facilities) so as not to require the file I/O support to be dragged into the linkages. The UTIL.C functions cputs () and putch () are similar to their namesakes in the Microsoft library and are provided here for the benefit of users of compiler packages that do not include these functions.

The setdrive() function I provide is cleaner than Microsoft's _dos_setdrive().

The library functions chdir() and getcwd() are used by the commands and should be available in your compiler's standard library.

When compiled with optimization, both CDE.EXE and RET.EXE weigh in at just over 6K, so their load-and-run time is negligible.

Caveats

The following line in your CONFIG.SYS file will insure plenty of environment space for the CHAIN variables:

shell = c:\command.com /p /e:1500
Due to an as-of-yet inexplicable MS-DOS anomaly, specifying too small a value for the environment (xxxx in /e:xxxx) may cause the system to hang up after CDE or RET completes execution. The message I've gotten says something about COMMAND.COM being "invalid". While this has never been destuctive, it has required a re-boot of the system. The only way I've found (so far) to avoid this problem is to allocate plenty of extra environment space. If anyone has a more "bulletproof" solution, please let us know here at CUJ.

I recommend highly that one modification be made to the Master Environment package as listed in the 7/89 CUJ: the environment variable name should be converted to upper case both in the m_getenv() and m_delenv() functions. As written, only the m_putenv() function converts the name to upper case, and this causes failure when either m_getenv() or m_delenv are called with lower-case variable names. To make this change, alter the lines reading:

n = name;
to:

n = strupr(name);
There is one such line near the beginning of both the m_getenv() and m_putenv() functions.

Linking

The commands to compile and link CDE.C and RET. C using Microsoft C are shown at the top of the source file listings. I arbitrarily named the master environment package ENVLIB.OBJ, so including envlib on the qcl command line links in the object module.

Summary

The CDE and RET commands provide a clean, quick and convenient mechanism for alleviating some of MS-DOS's command processor limitations. Although there are plenty of full-blown command processor replacements, shells and special-purpose TSRs out there (even for free) that offer alternative ways to "get around" your DOS system, few (if any) of these can offer 100% compatibility with all other packages and TSRs, zero bytes of system RAM overhead (unless you count the few extra bytes of environment space required), and virtually instantaneous gratification. And you even get the source code!