Why does each drive have its own current directory?

Commenter Dean Earley asks, "Why is there a 'current directory' AND an current drive? Why not merge them?"

Pithy answer: Originally, each drive had its own current directory, but now they don't, but it looks like they do.

Okay, let's unwrap that sentence. You actually know enough to answer the question yourself; you just have to put the pieces together.

Set the wayback machine to DOS 1.0. Each volume was represented by a drive letter. There were no subdirectories. This behavior was carried forward from CP/M.

Programs from the DOS 1.0 era didn't understand subdirectories; they referred to files by just drive letter and file name, for example, B:PROGRAM.LST. Let's fire up the assembler (compilers were for rich people) and assemble a program whose source code is on the A drive, but sending the output to the B drive.

A>asm foo       the ".asm" extension on "foo" is implied
Assembler version blah blah blah
Source File: FOO.ASM
Listing file [FOO.LST]: NUL throw away the listing file
Object file [FOO.OBJ]: B: send the object file to drive B

Since we gave only a drive letter in response to the Object file prompt, the assembler defaults to a file name of FOO.OBJ, resulting in the object file being generated as B:FOO.OBJ.

Okay, now let's introduce subdirectories into DOS 2.0. Suppose you have want to assemble A:\SRC\FOO.ASM and put the result into B:\OBJ\FOO.OBJ. Here's how you do it:

A> B:
B> A:
A> asm foo
Assembler version blah blah blah
Source File: FOO.ASM
Listing file [FOO.LST]: NUL
Object file [FOO.OBJ]: B:

The assembler reads from A:FOO.ASM and writes to B:FOO.OBJ, but since the current directory is tracked on a per-drive basis, the results are A:\SRC\FOO.ASM and B:\OBJ\FOO.OBJ as desired. If the current directory were not tracked on a per-drive basis, then there would be no way to tell the assembler to put its output into a subdirectory. As a result, DOS 1.0 programs were effectively limited to operating on files in the root directory, which means that nobody would put files in subdirectories (because their programs couldn't access them).

From a DOS 1.0 standpoint, changing the current directory on a drive performs the logical equivalent of changing media. "Oh look, a completely different set of files!"

Short attention span.

Remembering the current directory for each drive has been preserved ever since, at least for batch files, although there isn't actually such a concept as a per-drive current directory in Win32. In Win32, all you have is a current directory. The appearance that each drive has its own current directory is a fake-out by cmd.exe, which uses strange environment variables to create the illusion to batch files that each drive has its own current directory.

Dean continues, "Why not merge them? I have to set both the dir and drive if i want a specific working dir."

The answer to the second question is, "They already are merged. It's cmd.exe that tries to pretend that they aren't." And if you want to set the directory and the drive from the command prompt or a batch file, just use the /D option to the CHDIR command:

D:\> CD /D C:\Program Files\Windows NT
C:\Program Files\Windows NT> _

(Notice that the CHDIR command lets you omit quotation marks around paths which contain spaces: Since the command takes only one path argument, the lack of quotation marks does not introduce ambiguity.

Comments (39)
  1. Tor Lillqvist says:

    Doesn't the Win32 subsystem also keep track of the per-drive current directory, not just cmd.exe? How else can one pass drive-relative file names to CreateFile()?

  2. Clovis says:

    You can echo these if you want: 'echo %=c:%' works just fine.

    The clever thing about using the equals prefix for these magic variables is that you can't set them with the 'set' command because the equals is taken as a token separator so the syntax is wrong – set believes the variable name is missing. That removes the need for a chunk of code in cmd.exe that would have to add tracking to these magic variables so setting them did exactly what cd does.

  3. Alex Grigorievv says:


    And GetFullPathName uses them, too.

  4. Dan Bugglin says:

    TL;DR version of this post: Legacy compatibility :)

    MS-DOS batch files or programs will expect a current directory on every drive, thus you need one.

    Though I personally find it useful (or maybe I'm just used to it from the MS-DOS days) for the command prompt to remember multiple places where I was so I can quickly switch between them.  Trying to work on two different directories on the same drive quickly becomes annoying having to type out the full path name for every command, even with tab completion helping.

  5. ShuggyCoUk says:

    any relatively clean way to make CD be the equivalent of CD /D in cmd?

  6. ERock says:

    Thanks for the tip! I've wanted to change drive and directory for a long time but for some reason never thought to try "help cd" and find the switch. :P

  7. DWalker says:

    I didn't know about the /D parameter on Chdir either.  Learn something new every day…

  8. GregM says:

    Dan: you can use subst to assign one of those directories a new drive letter.

  9. Peter da Silva says:

    It's a rotten shame Microsoft didn't think to copy the solution Cromemco came up with for dealing with file hierarchy issues using CP/M style commands. What they did was have a flat UNIX file system, and VMS style assigned symbols. So "assign M: /a/source" "assign N: /b/destination" and…

    $ cd M:

    $ pwd


    $ asm foo

    Assembler version blah blah blah

    Source File: FOO.ASM

    Listing file [FOO.LST]: NUL

    Object file [FOO.OBJ]: N:

    Typically we had I: assigned to includes and L: assigned to the libs directory and passed to C80 and L80 on the command line.

    This was BEFORE MS-DOS 2.x

    [Yeah, it's a rotten shame nobody invented the SUBST command. -Raymond]
  10. Marquess says:

    Strange, I feel like I've read that before. Time must be going the other way again …

    @Peter da Silva

    That's the very reason subst was invented. And join, for the other way around.

  11. Rick C says:

    @ShuggyCoUk:  Yes, but it won't do what you want.

    c:>set cd=/d

    c:>cd d:test

    c:>  [huh?]


    d:test> [what the…?]


    c:>set cd=

    c:>cd /d d:test2


  12. Brian G says:

    @Rick C: what are you doing there?  Setting an environment variable named cd isn't remotely the same thing as aliasing 'cd /d' to be executed when the user types 'cd' at the prompt.

  13. Cherry says:

    I wonder who invented all those strange sytaxes. Why the hell does 'set "' (set, space, doublequote) show hidden env.vars like those =X: things? Or why does 'echo.' print an empty line?

    By the way, when I execute 'set "', I also get an variable like that: '=::=::'. I wonder why it is there. I don't have a drive '::' on my computer (although it's possible however, due to a bug in mountvol: call mountvol without parameters and write down the ID of some volume. Then enter 'mountvol :: {the ID you just wrote down}' and – baaam, you have a '::' drive. Try 'dir ::'. Changing the drive letter won't work however. But the mountvol trick works with all chars, you can also create a drive ù: or 4: or even ^G:, where ^G is Ctrl+G, which will result in a beep.).

  14. Henke37 says:

    It's an evolved language. It wasn't planed, designed or any of those fancy words. People sat down and wrote what they felt useful. Sometimes they documented what they did. Sometimes they didn't.

  15. RTM says:

    @Peter da Silva:

    Its a rotten shame no one reads the docs http://www.microsoft.com/…/subst.mspx

  16. Cooney says:

    If you really wanted, you could probably port bash to win32 (no, I mean a native win32, not cygwin) and get different syntax. Despite all appearances, you aren't actually tied to the nasty mess that is cmd.exe

  17. AC says:


    set "foo" displays all environment variables starting with foo. So set "" displays everything. And a missing end quotation mark is ignored.

    I do wonder if the behavior to show the hidden variables is an intended feature or a just a side effect of the implementation, though.

  18. Marquess says:


    Isn't bash already ported to Win32 via MinGW?

    On the other hand, I think I'll stick with cmd and PowerShell.

  19. daev says:

    Drive current directories lead to the interesting case of drive-relative paths; if the current path on drive D: is OBJ, then feeding CMD.EXE a path like "D:FOO.ASM" causes it to refer to "D:OBJFOO.ASM".  If you're working extensively with two directories, you can SUBST one of them to a drive letter and use drive-relative paths to keep your commands short and simple.

    This does lead to an interesting ambiguity.  Since the colon was later used for Alternate Data Streams, how should one interpret the path "D:FOO.ASM" in an environment where the current directory has a file named "D"?  It could be the ADS "FOO.ASM" in file D, or a drive-relative path on drive D:.

  20. Anonymous says:


    It also leads to another interesting question: how do you put an alternate data stream on the root of a drive letter?  "D::stream"?  Or does that mean the named stream attached to the current directory on D:?

    I think I ran into this weirdness before.  Maybe you had to introduce a ("d::stream") for that case.

    I guess for your case (a file called "D") you can make it unambiguous by specifying an absolute path ("C:Food:stream").

  21. benjamin says:

    just use the /D option

    Allow me to quote Chris Tucker and Ice Cube when I say "Daaaamn!"

  22. James says:

    @Marquess: IIRC, the MinGW bash is actually a cygwin bash with the cygwin bits statically linked in.

  23. James says:

    @ShuggyCoUk: You could use a Doskey macro to alias "CD=CD /D".  I made an alias file:

    CD=CD /D $*

    and then made a .cmd script:


    and then set the registry key:

    HKEY_CURRENT_USERSoftwareMicrosoftCommand ProcessorAutoRun

    set to invoke that script whenever I open a command prompt.

  24. To see all the magic environment variables:

    set "" | findstr ^^=

  25. gibwar says:

    @ShuggyCoUk, Rick C, James: Instead of finding a way to control the "cd" command, you can use the "pushd" and "popd" commands. They'll work cross drive letters, set those special command prompt variables, and provide the added ability to walk backward the directory stack.

    Very useful for batch files to quickly move to a new location, and get back to your original one regardless where it is.

  26. Rick C says:

    @Brian G:  Actually, many DOS commands let you set a variable with the same name as the command, which the command then uses as default options.  CD appears to be one of them, since the behavior is different depending on whether or not there's an environment variable.

    @gibwar:  I used pushd and popd all the time.

  27. David says:

    In 360KB-floppy-disk-only days, it was quite common for the A: drive to have your program, and the B: drive for your data. It makes sense to have drive-specific current directories for that scenario.

  28. Random832 says:

    The win32 functions [GetFullPathName and thereby everything else] do recognize these variables – and the documentation suggests that SetCurrentDirectory *ought* to set them [though in fact it doesn't, but the CRT chdir function does]. What's lacking is any [documented – I mean, it's obvious enough what effect SetEnvironmentVariable is likely to have on the present implementation] way to set it _without_ changing the drive at the same time.

    Also, the real current directory is more, er, real – since it's an open handle rather than simply a stored pathname.

  29. Evan says:

    @Coney: "If you really wanted, you could probably port bash to win32 (no, I mean a native win32, not cygwin) and get different syntax. Despite all appearances, you aren't actually tied to the nasty mess that is cmd.exe"

    Of course, that ties you to the nasty mess that is Bash. *Maybe* slightly less nasty than cmd, and certainly nasty in different ways, but still most decidedly quite nasty.

  30. Tor Lillqvist says:

    @Marquess, @James: You are confusing MSYS and MinGW. They are separate things, even if often used together.

    MSYS is a fork of an earlier version of Cygwin, and thus a POSIX emulation layer, with some special features to make interoperability with "native" Win32 programs easier. MSYS does include the bash shell.

    MinGW is the gcc compiler and related tools. They are normal "pure" Win32 programs, and can be used with no MSYS or other POSIX emulation involved in any way, if you want to.

    As far as I know, POSIX shells like bash would be virtually impossible to meaningfully port to "pure" Win32, without ifdeffing out most reasons to use it, I mean.

  31. px@i.kiev.ua says:

    [Yeah, it's a rotten shame nobody invented the SUBST command. -Raymond]

    Not command, but tool ;)

  32. Medinoc says:

    What bugs me more is that the current directory is a per-process variable and not a per-thread variable.

    One could argue compatibility, but it was worked around for the locale (with _configthreadlocale)…

  33. SuperKoko says:


    echo has always been an internal command with special treatment.

    MS-DOS 3.3 fixes the file extension when invoking a command so that:

    C:> program. asdf

    Is the same as

    C:> program.com asdf


    C:> program.dsflkjaoreiu asdf


    C:> program:sdf asdf

    However, the same command line is passed to the program:

    " asdf"

    MS-DOS >= 4 don't recognize this syntax anymore

    C:> program.

    -> Bad command or filename

    Test program:

    .model tiny


    org 100h


    mov bx,1

    xor cx,cx

    mov cl,[ds:80h]

    mov ah, 40h

    mov dx,81h

    int 21h


    end start

  34. I.D.GUY says:


  35. Miral says:

    Regarding the echo. thing — it's all to do with quirks of the command line processor.  See, back in ye olde days when you had COM files and the command line was passed in the 127 bytes of the PSP reserved for it, it so happened that the first byte of that space was the separator between the command and the rest of the arguments.  Since this was almost always a space, most programs ignored it and just used the remaining 126 bytes as the command line.  But it did mean that there was a difference between "echo" and "echo." and "echo ." — the command lines received were different in all three cases, and the defined behaviours were "display whether echo is on or off", "display nothing followed by a newline", and "display a dot followed by a newline".

    But why did it "display nothing" in the second case?  Because the command line wasn't empty (so it wasn't supposed to show whether echo was on or not) but the text to display (starting from the *second* character of the command line — remembering that the separator character gets skipped) was empty.  In fact it didn't really matter what that separator character was, as long as DOS regarded as the end of the command — you could use "echo:" or any of quite a few variations.  People just settled on "echo." because it was the least obtrusive.  Yes, it's a bit silly, but once "echo" was defined to produce some other output (and using quotes was defined to display those quotes) then there wasn't much else that could be done.

    And @Rick C: The %CD% environment variable contains the current working folder (provided it hasn't been redefined), not parameters that change the behaviour of the CD command.  To be consistent, that latter one would have been CDCMD, but that doesn't work.  Probably because it would break a lot of batch files.

  36. Clovis says:

    Because I.D.GUY's post is in upper case and I have the attention span of a moth, it has become the most important and useful thing I have read today. I've already emailed all my bank details and a photo showing where I keep the spare door key and I look forward to having my very own HOLOGRAM.

    What were we talking about again?

  37. Rick C says:

    @Miral:  I didn't know if CD supported that syntax, I just decided to try it and see…and it turns out not to work.

  38. Brian G. says:

    @Rick C: well there's the useful thing that *I* learned today!  Thanks!

  39. Myria says:

    It's even more interesting in Windows NT: In the NT kernel, there is no such concept as current directory.  The current directory is faked in user mode by kernel32.dll and ntdll.dll.  It does this by holding an open file handle to the directory logically represented as the current directory, and setting up child processes to inherit this handle.

    When you open a file in NT, you eventually call NtCreateFile (see ZwCreateFile in Windows Driver Kit).  The OBJECT_ATTRIBUTES structure contains a RootDirectory field.  If you put a file handle to a directory in the RootDirectory field, the filename you specify will be interpreted relative to that directory.  kernel32.dll thus implements current-directory-relative filenames by passing the current directory handle as the RootDirectory field.

    Linux buffs may recognize this mechanism as the "*at" series of file calls: openat, chmodat, unlinkat…  Another feature that NT got before Linux. =)

Comments are closed.

Skip to main content