Filesystem & Links
Inodes, hard links, symlinks, and where config lives.
Filesystem & Links
The filesystem is more than a tree of files and folders. Underneath, everything is an inode — a data structure that stores metadata and points to the actual data blocks on disk. Filenames are just labels that directories use to reference inodes.
Analogy
Think of a warehouse with numbered storage bins and a front-desk logbook. The bin (the inode) is where the actual stuff lives — weight, size, contents, who owns it. The logbook is just a list of nicknames mapped to bin numbers: "Summer Inventory → bin 4471". A hard link is writing a second nickname for the same bin — two entries in the logbook, one bin; scratch out one nickname and the other still reaches the stuff. A symlink is a sticky note that says "go look under this other nickname" — if that nickname is later erased from the logbook, the sticky note points at nothing and you get "No such file or directory". Mount points are a second warehouse whose loading dock has been welded to a specific aisle of the first, so to the forklift driver the building looks continuous.
Inodes
Every file has an inode. The inode stores:
- File size, permissions, owner, timestamps
- Pointers to the actual data blocks
The filename you see in ls is stored in a directory entry, which maps a name to an inode number. Multiple names can point to the same inode.
Hard links
A hard link is a second directory entry pointing to the same inode:
ln /etc/hosts /tmp/hosts-backup
Now /tmp/hosts-backup and /etc/hosts are two names for one file. The inode's link count is 2. Deleting /tmp/hosts-backup decrements the count to 1. The data is only removed when the link count reaches 0 and no process has the file open.
Hard links have two restrictions: they can't cross filesystem boundaries, and you should not hard-link directories (the kernel usually forbids it — it would create loops in the directory tree).
Symlinks
A symlink stores a path string, not an inode number:
ln -s /usr/local/bin/node /usr/bin/node
If /usr/local/bin/node is deleted, the symlink becomes dangling. Following it produces "No such file or directory". Unlike hard links, symlinks can cross filesystem boundaries and can point to directories.
| Hard link | Symlink | |
|---|---|---|
| Points to | Inode | Path string |
| Works across filesystems | No | Yes |
| Can link directories | No (usually) | Yes |
| Survives target deletion | Yes (data stays) | No (becomes dangling) |
Shown by ls -l |
No special mark | -> annotation |
Mount points
A mount point is a directory where a separate filesystem is grafted onto the tree. When you plug in a USB drive, macOS mounts it at /Volumes/MyDrive. The directory tree looks continuous, but the underlying storage is separate.
df -h shows mounted filesystems and their sizes. mount lists them all.
Paths — absolute and relative
An absolute path starts from /. It means the same thing regardless of where you are:
/Users/alice/code/project/README.md
A relative path starts from the current directory:
../../config/settings.json
$HOME and ~ both expand to your home directory. $HOME is an environment variable; ~ is expanded by the shell.
Where config lives
| Platform | Standard location |
|---|---|
| Linux (XDG) | ~/.config/appname/ |
| macOS (apps) | ~/Library/Application Support/appname/ |
| macOS (CLI tools) | ~/.config/appname/ (increasingly common) |
| Both | ~/.appnamerc or ~/.appname/config (older tools) |
On macOS, GUI apps use ~/Library/. CLI tools often follow the XDG spec and use ~/.config/. Some older tools use dotfiles directly in $HOME.
macOS case-insensitive filesystem
The default macOS filesystem (APFS) is case-insensitive but case-preserving. That means:
README.mdandreadme.mdare the same file as far as the OS is concerned.- But
lswill show you whichever capitalisation you used when creating the file.
This matters when you work with code that runs on Linux (case-sensitive). A file named Button.tsx won't be found by an import for button.tsx on Linux, even though macOS happily serves it.
find
find . -name "*.log" # files by name pattern
find . -type f -newer config.ts # files newer than a reference file
find . -type d -empty # empty directories
find . -name "*.tmp" -delete # find and delete in one pass
find respects symlinks cautiously: -L tells it to follow them.