Skip to main content
Linux · lesson 1 of 7
Linux beginner Lesson 1 of 7

The Linux Filesystem Hierarchy, and Why /bin Is a Symlink

ls -la / shows /bin, /lib, and /sbin as symlinks into /usr — not historical clutter, but the real result of a filesystem-layout change most distros made years ago.

By SkillByExample · Updated

linux filesystem fhs distro

ls -la / on a modern Linux system shows several directories that aren’t really directories — and that’s not an accident.

The real filesystem hierarchy

docker run -d --name linux-lesson --privileged ubuntu:22.04 sleep infinity
docker exec linux-lesson ls -la /
total 72
drwxr-xr-x   1 root root 4096 Sep 14 05:55 .
drwxr-xr-x   1 root root 4096 Sep 14 05:55 ..
-rwxr-xr-x   1 root root    0 Sep 14 05:55 .dockerenv
lrwxrwxrwx   1 root root    7 Sep  1 20:33 bin -> usr/bin
drwxr-xr-x   2 root root 4096 Apr 18  2022 boot
drwxr-xr-x  11 root root 3140 Sep 14 05:55 dev
drwxr-xr-x   1 root root 4096 Sep 14 05:55 etc
drwxr-xr-x   2 root root 4096 Apr 18  2022 home
lrwxrwxrwx   1 root root    7 Sep  1 20:33 lib -> usr/lib
lrwxrwxrwx   1 root root    9 Sep  1 20:33 lib32 -> usr/lib32
lrwxrwxrwx   1 root root    9 Sep  1 20:33 lib64 -> usr/lib64
lrwxrwxrwx   1 root root   10 Sep  1 20:33 libx32 -> usr/libx32
drwxr-xr-x   2 root root 4096 Sep  1 20:33 media
drwxr-xr-x   2 root root 4096 Sep  1 20:33 mnt
drwxr-xr-x   2 root root 4096 Sep  1 20:33 opt
dr-xr-xr-x 301 root root    0 Sep 14 05:55 proc
drwx------   2 root root 4096 Sep  1 20:42 root
drwxr-xr-x   1 root root 4096 Sep 14 05:55 run
lrwxrwxrwx   1 root root    8 Sep  1 20:33 sbin -> usr/sbin
drwxr-xr-x   2 root root 4096 Sep  1 20:33 srv
dr-xr-xr-x  11 root root    0 Sep 14 05:55 sys
drwxrwxrwt   1 root root 4096 Sep 14 05:55 tmp
drwxr-xr-x   1 root root 4096 Sep  1 20:33 usr
drwxr-xr-x   1 root root 4096 Sep  1 20:42 var

Look at the l at the start of the bin, lib, lib32, lib64, libx32, and sbin lines — every one of those is a symlink, not a real directory. bin -> usr/bin means every binary you’d expect to find at /bin/ls actually lives at /usr/bin/ls; the shorter path is kept only so decades of scripts and muscle memory that assume /bin/bash exists don’t break. This is the “usr merge” that Debian, Ubuntu, Fedora, and Arch have all adopted — a single real location for system binaries, which simplifies read-only root filesystems and atomic updates.

Identifying the distro and kernel

docker exec linux-lesson cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.5 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

/etc/os-release is a plain key-value text file every modern distro ships specifically so scripts can parse it reliably — ID and VERSION_ID are the two fields worth grepping for in an automated setup script.

docker exec linux-lesson uname -a
Linux cf95086ff556 5.15.153.1-microsoft-standard-WSL2 #1 SMP Fri Mar 29 23:14:13 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

uname reports on the kernel, not the distro — the -microsoft-standard-WSL2 suffix here gives away that this particular kernel is Windows Subsystem for Linux’s, regardless of which distro’s userland (Ubuntu, in this case) is running on top of it. Distro identity and kernel identity are genuinely separate questions, answered by separate commands.

/proc: a filesystem the kernel makes up on the fly

docker exec linux-lesson cat /proc/version
Linux version 5.15.153.1-microsoft-standard-WSL2 (root@941d701f84f1) (gcc (GCC) 11.2.0, GNU ld (GNU Binutils) 2.37) #1 SMP Fri Mar 29 23:14:13 UTC 2024

Nothing under /proc is stored on disk — it’s a virtual filesystem the kernel generates when you read it, which is why the exact same cat /proc/version returns live information rather than a cached file’s contents. This matters throughout the rest of this track: /proc is how later lessons inspect real running processes without needing any special tooling.

Practice

1. Confirm the kernel release alone, without the rest of uname -a's output.
docker exec linux-lesson uname -r
5.15.153.1-microsoft-standard-WSL2

-r alone is what a script checking a minimum kernel version should parse — grepping the full -a output for a version number is more fragile than asking for exactly the field needed.

2. Explain the permission string on /tmp and what makes it different from an ordinary world-writable directory.

Looking back at the ls -la / output: drwxrwxrwt on /tmp. Every user can create files there (rwx for owner, group, and others) — but the final t is the sticky bit. It means only a file’s own owner (or root) can delete or rename it, even though anyone can write into the directory. Without the sticky bit, any user could delete any other user’s temporary files.

3. Read a live value from /proc that changes between calls.
docker exec linux-lesson cat /proc/uptime
sleep 1
docker exec linux-lesson cat /proc/uptime
288973.71 3451867.57
288975.27 3451886.00

Two back-to-back reads of the same “file” return different numbers — the first field is seconds since boot, the second is total idle time summed across all CPUs. Nothing was written to disk between the two calls; the kernel computed a fresh answer both times. Notice the number itself is large (over 3 days) even though this container just started: a container doesn’t get its own kernel, so /proc/uptime here reports the host kernel’s uptime, not how long the container has been running — one more reminder that a container is an isolated process view on a shared kernel, not a separate machine.

Next: permissions and ownership — the bit-level mechanics behind a real permission-denied error.

Frequently Asked Questions

Why does /bin point to /usr/bin instead of being a real directory?
This is the 'usr merge' — most major distributions (Debian, Ubuntu, Fedora, Arch) now put every system binary in /usr/bin and keep /bin, /sbin, and /lib as compatibility symlinks. It exists because having a single real location for binaries simplifies read-only root filesystems and atomic OS updates; the symlinks exist purely so decades of scripts hardcoding /bin/bash or /sbin/ip don't break.
How do I find out what distro and version a Linux system is actually running?
Read /etc/os-release — it's a plain key=value text file every modern distro ships, with PRETTY_NAME, ID, and VERSION_ID fields meant to be parsed by scripts. uname -a gives kernel information (which is shared across distros), not distro identity — the two questions are different and need different commands.
What's actually different between uname -a and uname -r?
uname -r alone reports just the kernel release string; uname -a reports that plus the kernel name, hostname, build date, and machine architecture all on one line. Scripts that only need the kernel version (to check a minimum requirement, for instance) should use -r rather than parsing -a's combined output.
Is /proc a real directory on disk?
No — it's a virtual filesystem the kernel generates on the fly; nothing in /proc is stored on any disk. Reading a file under /proc (like /proc/version or /proc/[pid]/status) asks the kernel for live information at that instant, which is why the same read can return a different answer every time.
Why does /tmp have that unusual rwxrwxrwt permission string?
The final t is the sticky bit — anyone can create files in /tmp (world-writable, rwx for all), but the sticky bit means only a file's owner (or root) can delete or rename it, even though everyone can write to the directory. Without it, any user could delete any other user's temp files.