Robust File Naming

File names that survive contact with reality must work across every layer that touches them — filesystems, sync tools, shells, web servers, markup parsers, and personal knowledge tools — and each layer reserves its own characters. A name that is legal on ext4 may be un-cloneable on Windows, un-referencable in Markdown, or an injection vector in a shell loop. Robust naming is the intersection of all the reserved sets.

The avoid-list, by layer

  1. Non-ASCII — Unicode handling across tools/filesystems (NFC vs. NFD on macOS vs. Linux) is still too inconsistent to stake a filename on.
  2. Control characters — including tabs and newlines (legal on Unix, catastrophic in scripts).
  3. Exotic whitespace — plain spaces are now fine almost everywhere; non-breaking spaces and their kin are not.
  4. Filesystem-reserved: / \ : * ? " < > | — the union of NTFS, HFS+/APFS, and ext4 restrictions (Windows is the strictest common denominator; also avoid DOS device names like CON, NUL, aux).
  5. HTML/XML-reserved: & < > ' " — these surface as bugs in the weirdest places (exported HTML indexes, XML manifests).
  6. URL-reserved: ! " # $ % & ' ( ) * + , / : ; = ? @ [ ] — anything that will be percent-encoded is a future broken link.
  7. Tool-specific reserved — e.g. Obsidian/wiki links: # ^ [ ] |.
  8. Shell metacharacters: ` ~ ! # $ & * { } | \ / — legal in names, but a standing invitation to command-injection and globbing accidents (see bash-wildcard-abuse).

The practical rule

Stay inside [a-zA-Z0-9 , . _ -] plus plain spaces — or go stricter (lowercase-hyphen, like this wiki’s own convention) when names double as identifiers. Audit an existing tree for violations with:

find -E . -not -regex "^.*/[ a-zA-Z0-9',_.-]+$" -not -path "."

(GNU find: drop -E and use -regextype posix-extended.)

Sources