Filesystem conversion between / and :

I am aware that for filenames, somewhere between the UI components and the kernel a conversion between / and : happens. Can someone explain where this happens?

It is easy enough for me to convert ':' in kernel filenames to '/' in the string shown to or accepted from the user. But maybe there is an OS function I should be using to do that, in some more portable way, or something.

More challenging is what to do when the user really wants a ':' in their filename. What is best practice? I was a bit surprised to find that in Finder, if I try to create a file called 'abc/def:ghi', I get a vague error message that tells me to "try with no punctuation marks".

The case that has caused me to look into this now is a desire for filenames that are dates or times. From the user's point of view, a filename '8/10/2026 14:17:42' is not an unreasonable thing to ask for, is it?

What other oddities are there? '.' and '..' of course... anything else?

Answered by DTS Engineer in 908698022

To be clear, this whole situation is caused by macOS’s mixed heritage:

  • Traditional Mac OS used colon (:) as the path separator and thus slash (/) was allowed in file names.
  • BSD uses slash as the path separator and thus colon is allowed in file names.

Annoyingly, the canonical case where this crops up is dates and times, and neither of them support the most common English representations of both )-:

Early versions of Mac OS X had to do something to resolve this conflict, and the current situation is largely where we landed back then.

Can someone explain where this happens?

This is mostly at the UI layer. The most obvious example of that is, as Etresoft mentioned, the Finder, but there are others. For example, the standard save sheet accepts the name a/b, just like the Finder.

There are also some APIs that treat colons as path separators, and hence slashes as valid characters, but those are all deprecated. Mostly they are officially deprecated, but I’m sure you can rummage around and find ones that continue to escape official deprecation.

Amusingly, many of these APIs still work. Consider this code:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    
FSRef homeRef;
OSStatus err = FSPathMakeRef( (const UInt8 *) "/Users/quinn", &homeRef, NULL);
if (err != noErr) { abort(); }

NSString * name = @"Hello/Cruel/World!.txt";
unichar buffer[name.length];
[name getCharacters:buffer range:NSMakeRange(0, name.length)];
err = FSCreateFileUnicode(&homeRef, name.length, buffer, kFSCatInfoNone, NULL, NULL, NULL);
if (err != noErr) { abort(); }

#pragma clang diagnostic pop

It creates this file:

% ls -lh *.txt
-rw-r--r--  1 quinn  staff     0B  9 Oct 10:47 Hello:Cruel:World!.txt
But maybe there is an OS function I should be using to do that

For a specific file name you can use the various localised (or display) name APIs. For example:

let u = URL(fileURLWithPath: "/Users/quinn/Hello:Cruel:World!.txt")
let v = try u.resourceValues(forKeys: [.localizedNameKey])
print(v.localizedName!)
// Hello/Cruel/World!.txt

If you want to display a path, the best option is NSPathControl. But if you absolutely want to display a path as a string, my advice nowadays is to display the Posix path. Traditional Mac OS paths are now mostly a thing of the path (just don’t tell AppleScript!).

What other oddities are there?

There are so many. Lemme describe just a few:

  • Files with a leading dot (.) are generally considered invisible. The .isHiddenKey should take care of that for you.
  • Be prepared for different normalisations. HFS Plus converts everything to decomposed (Normalization Form D). APFS is normalisation insensitive but preserves it. Windows-y volume formats tend to use precomposed (Normalization Form C). And Unix-y volume formats tend to follow the ‘bag of bytes’ model *shudder*
  • That’s if you're working at the BSD level. Higher level APIs, most notably Foundation, can apply their own views on normalisation.
  • Similarly, be prepared for different approaches to case sensitivity.
  • There’s no easy way to establish a maximum file name length.
  • HFS Plus has its Unicode fixed in time, and thus you’ll see some very weird Unicode behaviours with it.

These are just the problems you face when reading the file system. when creating a file things get much much worse (-: Lemme know if you want to descend in that tarpit.

The folks over on Swift Evolution have recently been wrestling with this sort of thing while working on SE-0529 Add FilePath to the Standard Library. Notably, it ignores traditional Mac OS paths |-:

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I think this is strictly in the Finder. I can freely create files with colon in the Terminal. They work and look just like other files. I can't do that with a slash. I would have to find some Unicode fake slash.

And by the same token, I can't create a file with colon using the Finder. I have to use slash to make it a colon.

I say go beyond the Finder and reject both colon and slash. It's certainly reasonable for a user to want a timestamp in a file name. But just because it's reasonable doesn't mean they can have that. Bummer for user.

What other oddities are there? '.' and '..' of course... anything else?

Yes. There's a new ".nofollow" directory with really funky behaviour. If possible, treat it just like "." and ".." and ignore it. If it comes up during any processing, just skip that entry.

However, it may appear during bookmark resolution. If that happens, you should probably consider your bookmark to be dead and ask the user for the directory again. Otherwise, there's really no way to tell what will happen with that folder. This area is under active development.

To be clear, this whole situation is caused by macOS’s mixed heritage:

  • Traditional Mac OS used colon (:) as the path separator and thus slash (/) was allowed in file names.
  • BSD uses slash as the path separator and thus colon is allowed in file names.

Annoyingly, the canonical case where this crops up is dates and times, and neither of them support the most common English representations of both )-:

Early versions of Mac OS X had to do something to resolve this conflict, and the current situation is largely where we landed back then.

Can someone explain where this happens?

This is mostly at the UI layer. The most obvious example of that is, as Etresoft mentioned, the Finder, but there are others. For example, the standard save sheet accepts the name a/b, just like the Finder.

There are also some APIs that treat colons as path separators, and hence slashes as valid characters, but those are all deprecated. Mostly they are officially deprecated, but I’m sure you can rummage around and find ones that continue to escape official deprecation.

Amusingly, many of these APIs still work. Consider this code:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    
FSRef homeRef;
OSStatus err = FSPathMakeRef( (const UInt8 *) "/Users/quinn", &homeRef, NULL);
if (err != noErr) { abort(); }

NSString * name = @"Hello/Cruel/World!.txt";
unichar buffer[name.length];
[name getCharacters:buffer range:NSMakeRange(0, name.length)];
err = FSCreateFileUnicode(&homeRef, name.length, buffer, kFSCatInfoNone, NULL, NULL, NULL);
if (err != noErr) { abort(); }

#pragma clang diagnostic pop

It creates this file:

% ls -lh *.txt
-rw-r--r--  1 quinn  staff     0B  9 Oct 10:47 Hello:Cruel:World!.txt
But maybe there is an OS function I should be using to do that

For a specific file name you can use the various localised (or display) name APIs. For example:

let u = URL(fileURLWithPath: "/Users/quinn/Hello:Cruel:World!.txt")
let v = try u.resourceValues(forKeys: [.localizedNameKey])
print(v.localizedName!)
// Hello/Cruel/World!.txt

If you want to display a path, the best option is NSPathControl. But if you absolutely want to display a path as a string, my advice nowadays is to display the Posix path. Traditional Mac OS paths are now mostly a thing of the path (just don’t tell AppleScript!).

What other oddities are there?

There are so many. Lemme describe just a few:

  • Files with a leading dot (.) are generally considered invisible. The .isHiddenKey should take care of that for you.
  • Be prepared for different normalisations. HFS Plus converts everything to decomposed (Normalization Form D). APFS is normalisation insensitive but preserves it. Windows-y volume formats tend to use precomposed (Normalization Form C). And Unix-y volume formats tend to follow the ‘bag of bytes’ model *shudder*
  • That’s if you're working at the BSD level. Higher level APIs, most notably Foundation, can apply their own views on normalisation.
  • Similarly, be prepared for different approaches to case sensitivity.
  • There’s no easy way to establish a maximum file name length.
  • HFS Plus has its Unicode fixed in time, and thus you’ll see some very weird Unicode behaviours with it.

These are just the problems you face when reading the file system. when creating a file things get much much worse (-: Lemme know if you want to descend in that tarpit.

The folks over on Swift Evolution have recently been wrestling with this sort of thing while working on SE-0529 Add FilePath to the Standard Library. Notably, it ignores traditional Mac OS paths |-:

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks Quinn.

if you absolutely want to display a path as a string, my advice nowadays is to display the Posix path.

Right, that's what I was doing. But now I have a case where I want files corresponding to dates (but not times), which is why I'm considering doing the /: swap. This has the additional benefit (?) that the names match what is seen in Finder or the iOS Files app.

when creating a file things get much much worse

:-(

Here's what I'm actually doing:

I have a file format where the user can enter a document name which is stored internally in the file. And maybe the app provides a suggestion based on the date, e.g. "New widgets for 14/10/2026". I store that string verbatim internally in the document, and then I use it as the basis for the document's filename. At this point, I do /-to-: conversion. Maybe I convert : to space, or something. Maybe I remove newlines. And so on. But I don't want to reject names containing : (etc) because those are fine in the internal metadata. Oh, and I have to do something to force uniqueness i.e. '1:2' and '1 2' and '1\n2' would map to the same filename, ugh.

Later I may show a list or tree of available documents. At this point I may or may not have opened the document to read the internal metadata. So I may want to convert the filename back to (approximately) the name that that user entered.

Now that I write this, I do wonder if I should be escaping any :s, rather than converting them to e.g. spaces, since that avoids the uniqueness problem. Hmmm.

Storing metadata in a file name is universally recognized as a bad practice. It's also pretty much universally ignored.

When you're talking about "documents" and this kind of Finder-driven file name mangling, you're talking about user-created, stand-alone documents.

In this scenario, the Finder becomes your document database. And in most cases, that's fine.

But if you feel the need to take over file management from the Finder, then you have a lot more work to do. You will gain additional features and capabilities, but you have to do it carefully. And some ideas, like storing the file name inside the document, simply aren't viable in any context.

And easy way to solve this is to stop calling it "document name" and call it a "document description". Seems like a minor, pedantic point, but it's important. When you give something a label, that something tends to start adopting all known aspects of said label, whether they're appropriate or not.

You can certainly use the document description as a template for a document name suggestion in the file dialog. Since this is no long a "name" in any sense, you're free to mangle it in any way convenient. You should pre-process it to remove any characters that would reasonably cause confusion, i.e. any special or punctuation characters.

It's tempting to allow some common ones, but that's where you need to think about who's maintaining your document database. If you're taking on that responsibility, you'll need to think about how you would handle any file name with special characters or punctuation. Just go through the ones you can see on the keyboard and think of how many times each has caused you grief in the past.

So if you strip them all out, you give the NSSavePanel API guaranteed valid input. The user can change it and make it invalid, and the OS can change it to make it unique. But that's all outside of your concern and responsibility. And that's a great place for it.

If and when you get back a URL, you'll know it's valid, for at least the duration of your file coordination block.

And easy way to solve this is to stop calling it "document name" and call it a "document description".

I didn't invent the format, so I don't have this option.

Here's what I'm actually doing:

Sadly, I don’t have any easy solutions here )-:

If you try to display a path and include file names as they’re shown by the Finder then either:

  • You use a slash as the separator, whereupon the user can’t distinguish those slashes and the ones in the file names.
  • You use a colon as the path separator, which is staggeringly old school.

The way the Finder gets around this is to not render paths in the Posix style. You can see this in its path bar (View > Show Path Bar) and I think that’s also supported by NSPathControl.

When it comes to creating files, the only good way to know whether a name is acceptable is to try creating a file with that name. You can do various bits of preflighting — ban slashes, nul characters, leading dots, and so on — but nothing will give you 100% accurate results in the general case.

Given this reality, you’ll have to make some sort of compromise, and I think you’re the best person to judge what compromises work best for your app.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I didn't invent the format, so I don't have this option.

You're a human being. You can call it whatever you want.

But you can't assume that users or the operating system aren't going to rename the file.

Or, I guess when you open a document, you could check the "document name" and if it doesn't match the document name, then the document is corrupt. Then you can refuse to open it.

Personally, I don't think users would appreciate that. Maybe just explain how you didn't invent the format and I'm sure they'll understand.

Programmers have been creatively interpreting documented specifications for a long time. I'm not sure what the big deal is.