71 comments

[ 15.2 ms ] story [ 5291 ms ] thread
Since MS-DOS 2.0 :)
The exact answer is more like

> Newer Windows Versions (NT and later) copied that behaviour > Windows applications did not always follow and may or may not accept either when parsing for filenames.

Meaning it _may_ work but it also may not.

That's basically the story of every file name rule on Windows :)
There are two answers saying that, and one is sourced to Wikipedia whilst the other has screenshots of tests being run. Just copying Wikipedia to Stack Exchange isn't a good idea in general. In any case, there are plenty of contemporary 1980s discussions of SWITCHAR in CONFIG.SYS to be had, that go over the pathname separator characters as well. This has been well-known as a semi-official thing for 4 decades.

Heck, one could even cite Peter Norton on the pathname separators, writing the The dissection of DOS 3.0 article in his column in the 1984-10-30 edition (volume 3 number 21) of PC magazine.

> Meaning it _may_ work but it also may not.

The Windows API has always supported forward slash, going back to Windows 3.x, probably even back to 1.x and 2.x. For example, if you run NOTEPAD.EXE in Windows 3.1 [0], and type a path like c:/windows into the File>Open dialog box, it is accepted just as c:\windows would be

However, COMMAND.COM doesn't support forward slash, because of its use as an option character. (Unless you used the undocumented SWITCHAR setting to change the option character to a dash/hyphen, but that setting was disabled in DOS 3 onwards.) CMD.EXE partially has the same problem, although it can be worked around with quoting, which COMMAND.COM doesn't support.

Applications have always been a mixed bag though – including under that label Microsoft products, and even utilities included with DOS/Windows. Although, nowadays it is rare to find applications with that problem, but I'm sure if you go back far enough it would have been much more common. The explosion in popularity of the web and also of Linux and macOS has made Windows app developers (and even users) much more forward-slash-aware than they used to be.

[0] https://www.pcjs.org/software/pcx86/sys/windows/3.10/

I exploited that when writing a Java app that was targeting Linux and Windows about a decade ago. Got some backlash from colleagues about not using File.pathSeparator or whatever it was called, because the purists argued that we might one day support some platform where the forward slash doesn't work. When they failed to give an example it was decided to keep it that way. Makes for much more readable path names in code.

More than ten years later and I still think it was the right choice.

Heh, I was one of those purists! I do still prefer using `File.pathSeparator` but I fully concede that it is a useless and unneeded abstraction so my preference is likely purely religious.

All in all, I think the Java world suffers majorly from that extreme abstractions. Never in my life have I worked in a language/ecosystem with more (and often unnecessary) abstractions than in Java. Pretty good language overall, but the culture could really benefit from some good old fashioned pragmatism

The Path api will choose the right separator for you. Thinking about it really is a non-concern nowadays.
Yeah, usually I don't bother with manually inserting separators and instead use Path.join or whatever else. It'll do the right thing on basically all platforms.
They still have a point, though. There are virtually no platforms that don’t support forward slashes, yet there are abstractions in place to avoid simply using slashes. This may be irrelevant, but it’s still a useless layer of complexity.
I generally don't like to treat paths as strings just like I don't like to treat pointers as memory addresses. But hey, you don't always have to care about that stuff to make a functioning piece of software. You know, pick your battles, or however the saying goes.

(Not intended to be sarcastic, just in case it comes off that way. I only add the "useless layer of complexity" because it makes the intention more explicit for me.)

It is not a useless layer of complexity. Encapsulating paths in an object isn’t purely about slashes.
For what it's worth, .NET Core has the same capabilities of abstracting away pathname separators, if one wants it. Personally, I don't regard such things as useless and unneeded; because I see the value in not writing programs that break what has been a well-known piece of computer lore for 4 decades: namely that one can be Unix-like to a degree on non-Unix platforms such as MS-DOS, DOS+Windows, OS/2, and Windows NT.
According to [1], Mac supports using slashes as part of folder names (they use colons instead). So there might be some circumstances where that decision comes back to bite you, if you ever port to Mac.

[1]https://discussions.apple.com/thread/677339?sortBy=best

Heh, memory is foggy but I think one colleague occasionally ran it on his Mac without issues. I guess your linked thread is good advice, just dont't do it – like using spaces in filenames sooner or later makes a random shell script go haywire.
They used colon instead, back in pre OS X days. That’s >20 years ago. Admittedly some obscure cases carried that over and remain but you’ll rarely come across them today.
yes, if by 'mac' you mean macos 9 or earlier, which was a consideration when java was designed but generally not anymore
Oh it's even weirder than that: whether or not paths are separated by colons or slashes is specific to the app you're using, even in modern macOS versions.

To see this in action, open a terminal, type:

  touch test:file.txt
Then open Finder and browse to whatever folder you were in and you'll see a file called test/file.txt.

I'm not sure which is the native representation, but I do know that every API I've used to interact with the filesystem on macOS has used slashes as the path separator, so GP should be fine. But it's definitely a fun party trick to pull out around unsuspecting Mac users.

[flagged]
It's arguably a better idea than the dot-file- and /etc- mess on Linux tbh.

The Windows registry is just a system-wide key/value database.

I think the main point of this rant is how horrible regedit is. IMO it's a fair point.
But one that misses the point that regedit was superseded by regedt32 and even that hasn't been the only tool in the toolkit for decades.

There are specialized tools like (for instance) the ODBC Data Sources utilities that edit specific parts of the registry in more structured ways; and there are generalized tools like PowerShell that actually present the Registry as if it were a filesystem. One can actually "dir" the Registry with "-recursive" and wildcards and whatnot in PowerShell. (Personally, I try to be in the habit of avoiding PowerShell's compatibility aliases, but they're there for the people who want things to appear like their old familiar MS-DOS command lines.)

"I cannot edit this database file with a text editor." is an I-have-a-hammer-everything-must-be-a-nail argument. In the world of Windows and MS/PC-DOS preceding it, we've had specialized file editors for a long time.

I mentioned Peter Norton in another comment. Norton Utilities and clones like PC Tools and the Graham Utilities had specialized data file editors and viewers of several kinds. PC Tools could do several of the leading MS/PC-DOS database file formats, as I recall. (I'm not getting the box out of storage and checking the manual. (-: ) The culture of MS/PC-DOS has had the idea that more than just text editors exist for many years.

Easier to work with files, I can grep them, version them, diff them, edit them with any tool I want.
...if you actually know where to look in the first place though...
In /etc/$toolname

While in the registry it's like 5 layers deep and you need to figure out the vendor name along the way.

Application specific dot-files are sometimes in the user's home directory, but sometimes also elsewhere.
No, it's not there, it's in ~/.local. Or maybe ~/.cache. What is in /etc, however, is /etc/papersize. Not entirely sure who uses that, but it's there.

Also, regedit supports Ctrl+F.

Except that it is not /etc/$toolname.

Sometimes it's /etc/$subdirectory/$toolname, doing that very many-layers-deep thing that you describe.

Sometimes, particularly on the BSDs, it's /usr/local/etc.

Sometimes it's ~/.config/$subdirectory/$subdirectory/$thing . Or, if you are lucky, it follows the XDG rules for locating the ~/.config part.

Sometimes it's ~/.$toolname .

The irony is that all of the arguments about where the files are fall apart and miss the point, as the Windows registry hive file system is far more standardized than Linux-based operating systems and the BSDs are.

The real discussion point is whether a structured hierarchical database of key-value pairs with typed values, accessed through an operating system API, is better as an actual database file format, with all that that implies for things like indexing and the elimination of quoting rules, text parsing, and escaping; or as text files just like it used to be with the old loads-of-.INI files system of DOS-Windows 3.

You forgot about if the tool was installed using snap, then it is someplace different yet again!
I didn't want to spend a whole afternoon eumerating every single possibility. Anyway, I vaguely recall that I already did in a Stack Exchange answer, which is ironic given the headlined subject, years ago. (-:
The galaxy brain solution is to use nixos/home manager and design your own arbitrary layout config hydra.
Discovering what registry key controls whatever you are interested in is not at all easier than finding a configuration file, especially because what you need may be dispersed in several keys with obfuscated names that are located in wildly different parts of the huge registry.
In my experience, print -l /etc/**/*foo* or grep -Ir foo /etc leads to results faster than stabbing in the dark of regedit search. Manpages usually list configuration files.
> Easier to work with files, I can grep them, version them, diff them, edit them with any tool I want.

Microsoft could have exposed the registry as a filesystem. R: drive or \\winreg\ or whatever.

They kind of have with Powershell, but that's Powershell-specific. They could have made it part of the OS so it was available to all apps, like /proc and /sys on Linux. And then you could do all those things to them too.

In terms of the native API, it is viewed as a filesystem, accessed via the Object Manager like everything else. The registry is under \REGISTRY .
Not really, because although it is in the Object Manager namespace, it isn't composed of File objects, it is composed of Key objects. So the normal file APIs (e.g. NtOpenFile) don't work on it, instead you have to use NtOpenKey/etc

If it were actually a filesystem, it would be composed of File objects, so you could manipulate it using the exact same API calls you use to manipulate files – which would mean that tools/utilities/apps which know how to manipulate files would just magically work on registry keys too.

The registry can store binary values. Combining binary and text values in the same files usually result in a mess for most users.
I deeply dislike string-typed operating systems (i.e. Unix and its issue). Likewise with 'everything is a file'.

OSes are a superb use-case for recursive, object-oriented, hierarchical setup and configuration, but no; Unix and friends, and the programs that run on them, insist on spewing a million-odd .ini, .cfg, .json, .yaml, .xml, .whateverml files all over the filesystem and then 'hiding' them with the leading full-stop.

Actually, it's a combination of several such databases, some system-wide and some per-user.

And they're held in files. So if "I don't even give a damn what format the file is in" were true, registry hive files are not a problem per se. They are just files, too. They are just not text files, as many database files on computers are not.

Yep, I think the registry is far better than the mess of files on Linux. On Windows the answer to "where is this configured", 95% of the time, is the registry. I'm not saying it's perfect, because it isn't. But it's better than files scattered everywhere.
Not just a system-wide key/value database, but one with a few optimizations on top, if memory serves me right. It's in-memory first, which I suppose makes it a tad faster and is also neat for reducing disk writes on anything that receives a lot of changes in a short amount of time.

I think the concept is not too shabby. Perhaps not the best execution or tooling, but on paper, it sounds quite useful.

Another feature: .reg files!
I think the Windows registry is a classic example of a great idea in theory, but with regrettable quality of execution.

It could have been done a lot better. With a schema, self-documenting, with an integrated package database, with less insane legacy cruft.

But some of that's what you get with 30 years of history, with many decisions made in another era when resources were much more limited

Most if not all "problems" the Registry has are due to programmers like FwarkALark: Refusing to use anything other than scattered text files of varying formats and not cleaning up after themselves.

I love the Registry. It consolidates and centralizes almost everything there is to be configured in a single format, and it's even portable! Navigating it is no different than navigating a file system on a storage medium.

The Registry needs more use, not less.

Windows doesn't prohibit you from using better tooling.
Stepping aside for a moment, I chuckled at the ideas of "The Registry", as if it's some Government body in a dystopian world. Thank you for your comment.
My main gripe is "windows-aware" joinPath implementations which try to be helpful and use backslash as path separators when running on Windows (which then might be removed by other code which assumes that backslash is an escape-character). Just use forward slashes everywhere, it's fine and always has been fine since at least the mid-90s.
At risk of picking nits, I feel like if the output of a joinPath call is being used somewhere where escapes can be interpreted at all, something that has nothing to do with your choice of path separator is very wrong.
A common problem is building a command line with paths created with such a Windows-aware joinPath function and then executing that command line.

If that execution is handled by a shell instead of going directly through an operating system's CreateProcess() function, then the command line string may be mangled either by the shell, or some other "helpful" code sitting inbetween which tries to resolve backslash-escapes (which are usually needed in front of quotation marks).

It a very typical problem in cross-platform Python code, and I think I also stumbled over it in Node.js.

That sounds like an RCE waiting to happen.

The canonical way to solve this would be to pass the components of that command line through a function that escapes or quotes anything that needs to be escaped. In Ruby and when using bash for example we have [0].

Does Windows have something similar?

[0]: https://ruby-doc.org/stdlib-2.5.1/libdoc/shellwords/rdoc/She...

i only recently noticed that powershell accepts ~ as a shortcut for your user folder. anyone know how long that had been a thing?
Since 1.0, but it’s PowerShell specific.
In cmd you still have the very intuitive %USERPROFILE%. It can be shortened by a custom environment variable if you own the box.
Even in the Unix world, it's shell-specific, but most shells tend to expand ~ out to $HOME (which in turn gets expanded to the actual path). They also have complicated rules about when ~ characters are expanded or not.
PowerShell's ~ can be counterintuitive, as it's relative to the current location's path and defined by a property of the current location's PSProvider:

  PS C:\> Get-PSDrive C,S,HKLM | select Name,{$_.Provider.Name},{$_.Provider.Home}
  
  Name $_.Provider.Name $_.Provider.Home
  ---- ---------------- ----------------
  C    FileSystem       C:\Users\jtm
  S    FileSystem       C:\Users\jtm
  HKLM Registry
  
  PS C:\Users\jtm> Get-PSDrive C,S,HKCU,HKLM | select Name,{$_.Provider.Name},{$_.Provider.Home}
  
  Name $_.Provider.Name $_.Provider.Home
  ---- ---------------- ----------------
  C    FileSystem       C:\Users\jtm
  S    FileSystem       C:\Users\jtm
  HKCU Registry
  HKLM Registry
  
  PS C:\Users\jtm> cd HKLM:
  PS HKLM:\> cd ~
  Set-Location: Home location for this provider is not set. To set the home location, call "(get-psprovider 'Registry').Home = 'path'".
  PS HKLM:\> (Get-Location).Provider.Home = 'C:\Program Files\Microsoft Office'
  PS HKLM:\> cd ~
  PS C:\Program Files\Microsoft Office> (Get-Location).Provider.Home = 'HKLM:\SYSTEM\CurrentControlSet'
  PS C:\Program Files\Microsoft Office> cd S:
  PS S:\> cd ~
  PS HKLM:\SYSTEM\CurrentControlSet> (Get-Location).Provider.Home = '..'
  PS HKLM:\SYSTEM\CurrentControlSet> cd ~
  PS HKLM:\SYSTEM> cd ~
  PS HKLM:\> Get-PSDrive C,S,HKCU,HKLM | select Name,{$_.Provider.Name},{$_.Provider.Home}
  
  Name $_.Provider.Name $_.Provider.Home
  ---- ---------------- ----------------
  C    FileSystem       HKLM:\SYSTEM\CurrentControlSet
  S    FileSystem       HKLM:\SYSTEM\CurrentControlSet
  HKCU Registry         ..
  HKLM Registry         ..
  
  PS HKLM:\>
Another interesting thing about Windows CMD:for most of internal commands, you can omit the space between the command and switch that starts with a slash (a common thing in Windows).

E.g. the following two are the same:

    ipconfig/all
    ipconfig /all
I always wonder how exactly this is implemented (and why?!).

Edit: oh, another more famous example is the equivalence of

    cd..
    cd ..
And I have to admit I still do `ls..` from time to time, since I used Windows first (and most).
I guess only M$ can answer such question. My best guess would be that slashes are not allowed in file names and thus can be treated as delimiter and thus the cmd can actually try to interpret the part before the slash as the command and execute it.
As the author of a CMD replacement long ago, I remember Rex Conn's comments on how the command-line parser of COMMAND (and hence CMD) was quite arbitrary.

What was implemented, though, was a set of characters that acted as separators between the command name and the rest of the command tail. To many people's surprise in years gone by, this included not just whitespace but things like forward slashes and equals signs and commas. If memory serves, there was even an undocumented NLSFUNC table for looking them up, although my memory might be mis-serving me here.

So one could, for example, treat the COMMAND command line as if it were CONFIG.SYS and do SOMETHING=ARGUMENTS, which was particularly useful for the built-in PATH and DPATH commands.

MS/PC/DR-DOS, and indeed OS/2, programs were passed not a set of arguments but a single command tail, limited to 126 characters on MS/PC/DR-DOS and limited to 64KiB for the whole command-tail(s)-plus-environment-strings on OS/2. (It was the runtime libraries of programs written in programming languages such as C that did of all of the fakery that pretended that there was an argv[], and notably there were slight disagreements between Microsoft, Borland, and Watcom on the wild edge cases.) Usually, the first character of this command tail was the whitespace separator character that got entered by the user. But it didn't have to be.

(comment deleted)
(comment deleted)
Yes, it has worked for ages.

the fact that CMD.EXE (or COMMAND.COM in earlier versions) famously did (and still does) NOT support it has many people confused.

> the fact that CMD.EXE (or COMMAND.COM in earlier versions) famously did (and still does) NOT support it has many people confused.

CMD.EXE does support forward slash paths. But some of its commands you have to quote a path containing forward slashes, or else it will think it is an option character.

For example, `type c:/windows/win.ini` will give a syntax error. But `type "c:/windows/win.ini"` will do what you expect. By contrast, `cd c:/windows` just works.

COMMAND.COM is a different story, in part because unlike CMD.EXE, COMMAND.COM doesn't support quoting of paths. Although, while CMD.EXE is fine with `cd c:/windows`, COMMAND.COM rejects it.

[0] Amazing that win.ini is still there after all these years, but it is just a vestige for 16-bit app support–although still present on 64-bit systems that don't support 16-bit apps.

`cat foo/ba<tab>` doesn’t work anyway. Inability to complete paths ruins the slash.
That’s just a CMD problem. Powershell does this perfectly.
I just wish that Linux would be less obnoxious about file separators and especially line endings. Windows mostly just eats linux-style line endings and file paths without a problem, and MOST programs do not have a problem either.

Feed a windows-style path or line ending to linux? Watch how it dies with absurd errors.

But \ is a valid character for a filename in Linux. It can't also be a separator.
And, should you check such a file into a git repo, you won't be able to clone it on Windows. With that in mind I would encourage never doing so (and makes me wish git had a filter to prevent it in the first place or at least provide a stern warning.)
Always did? In code. Not in shells.
Since the '90s. While the preferred form is '\', '/' was allowed as an alternate path separator.
CreateFile and friends will indeed treat "/" and "\" the same, it's even documented that way these days. Except if you are ever in a corner and need to prepend the path with "\\?\" to get around other limitations.

For a while, I think it was the only supported way to have paths longer than 260 characters, so I've seen some high level APIs add it to the path to work around issues (C# used to do this in some paths, not sure if it still will).

It might be rare, but as someone that's had to debug a "why doesn't c:/whatever/data.dat work but c:\whatever\data.dat does?" issue, I err on using the correct file separator.