Before you read. A Linux machine has run out of disk space. You have not installed anything new and you have not saved any files. Something has been quietly filling the disk on its own for months. Which one directory would you look in first, and what kind of thing would you expect to find there?
You are not expected to know the answer yet. Have a guess anyway.
On Windows your programs live in C:\Program Files and your documents live in
C:\Users\you\Documents. Somebody decided that once, and now everybody follows
it. Linux has the same kind of agreement, written down as a standard, and it is
what this topic is about.
Here is why it is worth an hour rather than a glance. On Windows the folder names are mostly labels. In Linux they encode decisions: what is safe to share between machines, what can be locked read-only, and what is deliberately thrown away every time the machine restarts. Once you can see the decisions, you can work out where a file belongs rather than memorising twenty folder names.
Some words you will need
- directory
- A folder. Linux documentation says directory, so this track does too.
- path
- The address of a file, written with forward slashes:
/etc/hosts. Linux uses/where Windows uses\. - root directory
- The very top, written as a single
/. Everything on the system hangs underneath it. There are no drive letters; a second disk appears as a directory somewhere under/rather than asD:. - distribution
- A packaged version of Linux, such as Ubuntu, Debian, or Red Hat Enterprise Linux. Often shortened to "distro". They share almost everything and differ in specific, testable ways.
- package
- Installable software, roughly the equivalent of an installer on Windows. The program that installs them is a package manager.
What breaks without this
Three failures, all cheap to avoid and expensive to debug.
You put a file somewhere that gets wiped. You need to save something, you
find a directory you are allowed to write to, and you use it. If that directory
was /run or /tmp, the system deletes the contents on reboot. On purpose.
Your file works fine for a week, someone restarts the machine, and it is gone
with no error and nothing in a log.
You write instructions that only work on half of Linux. The command to install software on Ubuntu is not the command to install software on Red Hat. Neither are some of the paths around it. A script you tested on one machine fails on another, and the error message names a missing program rather than the assumption you made.
You trust a command that quietly gives the wrong answer. There is one further down this page that does exactly that, and it catches people with years of experience, because the command is correct and the answer is still wrong.
The mental model
Start with one directory rather than the whole tree.
Say you install a web server. Its program file has to go somewhere, its settings file has to go somewhere, and the log it writes has to go somewhere. Three files, three different kinds of thing:
- The program is identical on every machine that installed the same version. Nobody edits it. It gets replaced when you upgrade.
- The settings are specific to this machine, and you do edit them. Change the settings and you change what this server does.
- The log grows on its own, forever, without anybody touching it.
Linux puts those in three different places for that reason: the program in
/usr, the settings in /etc, the log in /var. Not by tradition. Because
they behave differently.
Generalise that and you have the whole standard. Every directory sits somewhere on two questions: does the content change by itself while the machine runs, and is it the same on every machine or specific to this one?
Read each corner as an answer to those two questions:
/usrdoes not change by itself, and is the same everywhere. This is where installed programs live. Because nothing on the running machine writes to it, it can be made read-only, or stored once and shared with many machines./etcdoes not change by itself, but is specific to this machine. The settings. This is the directory that makes this server this server, which is why it is the one to back up./varchanges constantly. Logs, mail queues, caches, databases. It grows whether you touch it or not, which is why it is the usual reason a machine runs out of disk./runchanges constantly and is thrown away on every restart. It holds notes the running system keeps for itself, such as which processes are alive. Nothing durable should ever be put here.
If you already administer Linux: the standard's own vocabulary
The two axes are what the Filesystem Hierarchy Standard calls static versus
variable and shareable versus unshareable. The practical consequences are
the ones worth carrying: /usr being static and shareable is what makes a
read-only /usr and network-mounted /usr possible, and it is the assumption
behind image-based systems and containers, where the OS layer is immutable and
everything writable is a mount.
/run is a tmpfs, a filesystem that exists only in memory, which is why it is
empty after boot without anything having to delete it. Before it existed this
state lived in /var/run, which on modern systems is a symlink to /run for
compatibility.
The directories worth committing to memory are the ones where getting it wrong costs you something:
| Directory | Holds | The reason it matters |
|---|---|---|
/etc |
System-wide configuration, no binaries | The machine’s identity. Back this up. |
/usr |
Installed software: /usr/bin, /usr/lib, /usr/share |
Owned by the package manager. Do not hand-edit. |
/var |
Logs, spools, caches, service data | Fills up. Give it its own filesystem on a server. |
/opt |
Self-contained third-party software | Vendor drops that do not follow the FHS live here. |
/srv |
Data served by this system | Web roots and exports, by convention rather than enforcement. |
/boot |
Kernel, initramfs, bootloader config | Often a small separate partition, which is why it fills. |
/proc |
Kernel and process interface | Not on disk. Files here are generated on read. |
/sys |
Device and driver interface | Also not on disk. |
/run |
Runtime state, PID files, sockets | Cleared every boot. Never put anything durable here. |
/tmp |
Scratch space | May be cleared on boot or on a timer. Assume it will be. |
/proc and /sys catch people out because they look like directories and are
not. Nothing there is stored anywhere; reading a file runs kernel code that
produces the answer. That is why cat /proc/cpuinfo is instant and why you
cannot back /proc up.
The usr-merge, and what it broke
For most of Linux’s history /bin and /usr/bin were genuinely different
directories, split because early systems had a small root disk and mounted
/usr separately. That distinction stopped being useful once initramfs existed,
and every mainstream distribution has since collapsed it: /bin, /sbin,
/lib, and /lib64 are now symlinks into /usr.
You can see it directly.
Here is the root of a current Debian system. Read the first character of
each line: d for a directory, l for a symbolic link.
/bin, /lib, and /sbin have been separate directories since the 1970s. On a system from the last few years, are they still directories?
# Debian 13 (trixie), x86_64
$ ls -l / | head -25
total 0
lrwxrwxrwx. 1 root root 7 Jul 4 09:05 bin -> usr/bin
drwxr-xr-x. 2 root root 6 Jul 4 09:05 boot
drwxr-xr-x. 5 root root 340 Aug 7 19:25 dev
drwxr-xr-x. 1 root root 31 Aug 7 19:25 etc
drwxr-xr-x. 2 root root 6 Jul 4 09:05 home
lrwxrwxrwx. 1 root root 7 Jul 4 09:05 lib -> usr/lib
lrwxrwxrwx. 1 root root 9 Jul 4 09:05 lib64 -> usr/lib64
drwxr-xr-x. 2 root root 6 Aug 3 00:00 media
drwxr-xr-x. 2 root root 6 Aug 3 00:00 mnt
drwxr-xr-x. 2 root root 6 Aug 3 00:00 opt
dr-xr-xr-x. 216 nobody nogroup 0 Aug 7 19:25 proc
drwx------. 2 root root 37 Aug 3 00:00 root
drwxr-xr-x. 1 root root 42 Aug 7 19:25 run
lrwxrwxrwx. 1 root root 8 Jul 4 09:05 sbin -> usr/sbin
drwxr-xr-x. 2 root root 6 Aug 3 00:00 srv
dr-xr-xr-x. 12 nobody nogroup 0 Aug 7 19:25 sys
drwxrwxrwt. 2 root root 6 Aug 3 00:00 tmp
drwxr-xr-x. 12 root root 133 Aug 3 00:00 usr
drwxr-xr-x. 11 root root 139 Aug 3 00:00 var
Three of them are symlinks now. bin, lib, lib64, and sbin all
begin with l and point into /usr. Fifty years of documentation, scripts,
and exam material describe them as directories, and on any current system they
are not, which is the whole of the next few paragraphs.
The RHEL family did the same thing, earlier. AlmaLinux 10 looks almost identical,
with the addition of /afs and without a /boot entry inside a container image:
# AlmaLinux 10.2, x86_64
$ uname -m; ls -l / | head -6
x86_64
total 8
dr-xr-xr-x. 2 root root 6 Apr 2 2025 afs
lrwxrwxrwx. 1 root root 7 Apr 2 2025 bin -> usr/bin
drwxr-xr-x. 5 root root 340 Aug 7 19:27 dev
drwxr-xr-x. 43 root root 4096 Jun 2 11:08 etc
drwxr-xr-x. 2 root root 6 Apr 2 2025 home
The practical consequence is that /bin/ls and /usr/bin/ls are the same file.
The consequence people get caught by is what happens when you ask a package
manager about it.
Telling the families apart from the system itself
The split that matters most on this exam is RPM-based against dpkg-based, because it decides which package manager, which config paths, and often which service names you get. Do not infer it from the hostname. Read it.
/etc/os-release is the answer, and it is the one file that is present and
consistent everywhere, because systemd standardised it.
# AlmaLinux 10.2, x86_64
$ . /etc/os-release; printf "%s | %s | %s\n" "$ID" "$VERSION_ID" "$PRETTY_NAME"
almalinux | 10.2 | AlmaLinux 10.2 (Lavender Lion)
# Debian 13 (trixie), x86_64
$ . /etc/os-release; printf "%s | %s | %s\n" "$ID" "$VERSION_ID" "$PRETTY_NAME"
debian | 13 | Debian GNU/Linux 13 (trixie)
# Ubuntu 24.04 LTS, x86_64
$ . /etc/os-release; printf "%s | %s | %s\n" "$ID" "$VERSION_ID" "$PRETTY_NAME"
ubuntu | 24.04 | Ubuntu 24.04.4 LTS
# openSUSE Leap 16.0, x86_64
$ . /etc/os-release; printf "%s | %s | %s\n" "$ID" "$VERSION_ID" "$PRETTY_NAME"
opensuse-leap | 16.0 | openSUSE Leap 16.0
The file is shell syntax on purpose, so sourcing it is the intended way to read
it rather than a trick. For derivative distributions there is also ID_LIKE,
which is how you detect a family rather than a specific product: Ubuntu reports
ID_LIKE=debian, AlmaLinux reports ID_LIKE="rhel centos fedora".
If you already administer Linux: /opt, /usr/local, and /srv, and who is allowed to write where
Three directories that all mean “not from the distribution”, with a real division of labour the standard is specific about.
/usr/local is for software you built or installed yourself, and it
mirrors /usr underneath: /usr/local/bin, /usr/local/lib,
/usr/local/etc. Nothing from a package manager writes here, which is exactly
the point: it is the one place you own on a machine whose /usr belongs to
the distribution. make install defaults to it for that reason.
/opt is for self-contained third-party packages, each in its own directory
named for the vendor or product: /opt/vendorname/. Commercial software uses
it because it wants one tree it can install and remove atomically rather than
files scattered through the hierarchy.
/srv is for data this machine serves. Web roots, FTP trees, exported shares.
It is the one that gets ignored, with web content ending up in /var/www,
which is a Debian convention rather than the standard’s answer. Either works;
consistency across a fleet is what matters.
The operational value of the split: a file under /usr that no package owns is
an anomaly worth investigating; the same file under /usr/local or /opt is
somebody doing the right thing. That distinction is what makes rpm -qf and
dpkg -S returning nothing meaningful rather than merely true, and it is the
lesson 08 point arriving from a different direction.
Across distributions
| RPM family | dpkg family | |
|---|---|---|
| Examples on this exam | RHEL, AlmaLinux, Rocky, Fedora, openSUSE, SLES | Debian, Ubuntu |
| Low-level tool | rpm |
dpkg |
| Resolver | dnf (zypper on SUSE) |
apt |
| Which package owns a path | rpm -qf PATH |
dpkg -S PATH |
| Package file extension | .rpm |
.deb |
| Web server package | httpd |
apache2 |
That last row is the shape of a whole class of differences: the same software, a different package name, and therefore a different service name and a different config path. It is not a detail you can reason your way to, which is why the exam asks about it.
Two ways of splitting the same distributions, and the track uses both. The
table above splits on package format, which is why it says RPM family and dpkg
family. Every later topic says RHEL family and Debian family instead,
because most of what differs between machines is a decision the distribution
made rather than a consequence of rpm or dpkg: which group grants sudo,
where the auth log lives, what the default root filesystem is.
The two groupings are nearly the same and not quite. openSUSE and SLES use
rpm, so they sit in the RPM family, and they are not RHEL derivatives: they
resolve packages with zypper rather than dnf, and they default to Btrfs on
root where RHEL uses XFS. When this track says “RHEL family” it means RHEL,
AlmaLinux, Rocky, and Fedora. When a difference is genuinely about the package
format, it says RPM family and includes SUSE.
One exception inside that family, since this track keeps saying the RHEL side uses XFS. Fedora’s desktop variants have installed onto Btrfs since Fedora 33. Fedora Server, Cloud, IoT, and CoreOS were excluded and still use XFS, so the claim holds for the server installs this exam is about and not for a Fedora Workstation laptop.
If you already administer Linux: what the usr-merge actually bought, and the three places it still bites
Collapsing /bin into /usr/bin looks like tidying. The motivation was
operational, and knowing it explains why every distribution did it within a few
years of each other.
The original split was a 1970s disk-space accident. Ken Thompson and
Dennis Ritchie filled the RP03 holding / and moved the overflow to the
second disk, mounted at /usr. The rule that followed, “/bin holds what you
need before /usr is mounted”, was rationalisation after the fact, and it
stopped being true once initramfs took over early boot in the 2000s.
What the merge bought:
/usrcan be a single read-only, verifiable, shareable image. Everything the distribution ships is in one subtree, so it can be mounted read-only, checksummed, atomically swapped, or shared between containers. That is the foundation of image-based systems like Fedora CoreOS and ofostreegenerally.- A clean split between vendor data and machine state.
/usris the vendor’s;/etcand/varare the machine’s. Factory reset becomes “discard/etcand/var”, which is genuinely useful. - The end of “which copy of this binary is real”. Two
bindirectories on$PATHwas a real source of confusion.
Three places it still bites:
dpkg -S /bin/ls fails on Debian while dpkg -S /usr/bin/ls works. The
package database records the canonical path, and dpkg does not resolve the
symlink for you. rpm -qf does resolve it, so the same query behaves
differently by family, worth knowing before concluding a file is unowned.
Shebangs written #!/bin/bash still work, because the symlink resolves, but a
script hardcoding /bin in a comparison against $PATH entries will not
match. Anything doing string equality on paths is suspect.
find / -name ls reports it twice unless you use -xdev or account for the
symlink, because the tree really is reachable by two routes. That inflates any
audit script that counts files, including some setuid inventories.
The one exception nobody merged is /sbin. It was merged into /usr/sbin,
but Debian went further in trixie and made /usr/sbin a symlink to /usr/bin
too, on the grounds that the split between “commands for administrators” and
“commands for everyone” was never enforced by anything. So on a current Debian
all four paths reach one directory.
Server architectures, and the two names for each one
Four architectures are worth knowing by name: x86 (32-bit), x86_64 (also written AMD64, the 64-bit extension and the default for servers), AArch64 (64-bit ARM, now common in cloud instances and in every Apple Silicon Mac), and RISC-V, an open instruction set architecture that is still mostly outside production but is named because it is arriving.
The trap is that the same machine reports two different architecture strings depending on who you ask.
# Debian 13 (trixie), x86_64
$ uname -m; dpkg --print-architecture
x86_64
amd64
# Debian 13 (trixie), aarch64
$ uname -m; dpkg --print-architecture
aarch64
arm64
# AlmaLinux 10.2, x86_64
$ uname -m; rpm -E "%{_arch}"
x86_64
x86_64
uname -m reports the kernel’s name for the machine. dpkg uses Debian’s own
architecture names, which are different words for the same thing: amd64 for
x86_64, arm64 for aarch64. rpm uses the kernel’s name unchanged.
So on a Debian-family system, a script that compares uname -m against a
package architecture will never match, and the mismatch is not an error - it is
two naming schemes for one machine. When you need the packaging name, ask the
packaging tool.
The graphical stack, briefly
Servers usually have none of this, but the exam names the pieces and they stack in a specific order:
| Piece | Job | Examples |
|---|---|---|
| Display server | Talks to input devices and the screen; draws nothing itself | X.Org (X11), Wayland compositor |
| Display manager | The graphical login prompt, starts the session | GDM, SDDM, LightDM |
| Window manager | Decorates, moves, and stacks windows | Mutter, KWin, i3 |
| Desktop environment | The window manager plus a full set of applications | GNOME, KDE Plasma, Xfce |
The distinction that gets tested is X versus Wayland. X.Org is a display server that clients connect to over a socket, with the window manager a separate client telling X where to put things. Wayland collapses that: the compositor is the display server and the window manager at once, and clients draw their own windows. That is why “X forwarding over SSH” has no direct Wayland equivalent, and why disabling X forwarding shows up as a hardening step later in this track.
Software licensing
Four terms, and the difference between two of them is the one people get wrong.
- Free software is about the recipient’s rights: to run, study, modify, and redistribute. “Free” as in freedom, not price.
- Open source describes largely the same set of licenses with the emphasis on the development model rather than the ethics.
- Copyleft is a specific mechanism, not a synonym for either. A copyleft license requires that derivative works carry the same license, so the freedoms cannot be stripped downstream. The GPL is copyleft; MIT and BSD are open source and permissive, which means a derivative may be closed.
- Proprietary software withholds some of those rights, typically the source itself.
The practical consequence in a work setting: permissive and copyleft licenses have genuinely different obligations when you ship a product built on them, which is why the distinction is on a sysadmin exam at all.
If you already administer Linux: which directories are memory, and which are not really files
df -h on a modern machine lists a dozen filesystems that are not on any disk,
and knowing which is which prevents a category of confusion.
tmpfs lives in RAM and is gone at reboot. /run is always one, /dev/shm
is always one, and /tmp is one on most current distributions. Two consequences:
writing a large file to /tmp consumes memory, not disk, and a process that
fills /dev/shm can starve the machine while df on the root filesystem looks
healthy. findmnt -t tmpfs lists them.
/proc is not a filesystem at all in any meaningful sense. Every file in it
is generated by the kernel at the moment you read it, which is why
/proc/meminfo has a size of zero and content that changes every time.
/proc/<pid>/ is the per-process view (cmdline, environ, cwd, fd/)
and ls -l /proc/1234/fd listing open file descriptors is how you find what
is holding a deleted file open when df and du disagree.
/sys is the device model, one file per attribute, and it is writable in
places. Tuning knobs live under /sys/class/ and /sys/block/, which is how
you change a disk’s I/O scheduler or a network card’s queue length without a
reboot, and lose it at the next one unless it is written into udev rules or
sysfs.conf.
/boot may be a separate real filesystem with very little room, and a full
/boot is what stops a kernel update completing. It is worth being on the
list of things to check before a patch window, alongside the initramfs
question from lesson 09.
Prove it
Three checks that tell you what you are on and confirm the layout is what you think it is.
# Which distribution and family
. /etc/os-release && echo "$ID (${ID_LIKE:-none}) $VERSION_ID"
# Confirm the usr-merge is in effect: these should be symlinks, not directories
ls -ld /bin /sbin /lib
# Confirm /bin/ls and /usr/bin/ls are one file, not two copies
stat -c '%i %n' /bin/ls /usr/bin/ls
The third one is the check worth internalising. stat -c '%i' prints the inode
number. If the two paths report the same inode, they are the same file on the
same filesystem, and any difference in how a tool answers questions about them
is coming from the tool, not from the disk.
What trips people up
1. dpkg -S and rpm -qf disagree about /bin/ls
Both systems have /bin symlinked to usr/bin. Both have exactly one ls. Ask
each package manager who owns /bin/ls and you get different answers.
One of these two commands fails. Which, and what does that tell you about where each tool looks?
# AlmaLinux 10.2, x86_64
$ rpm -qf /bin/ls; rpm -qf /usr/bin/ls
coreutils-single-9.5-7.el10.x86_64
coreutils-single-9.5-7.el10.x86_64
# Debian 13 (trixie), x86_64
$ dpkg -S /bin/ls; echo "exit=$?"; dpkg -S /usr/bin/ls
dpkg-query: no path found matching pattern /bin/ls
exit=1
coreutils: /usr/bin/ls
rpm -qf resolves the path on the live filesystem before looking it up, so the
symlink is followed and both spellings land on the same entry. dpkg -S does a
string match against the paths recorded in the package database, and Debian’s
database records /usr/bin/ls. /bin/ls is not in the database because no
package ever claimed that spelling.
Neither tool is broken. They are answering different questions: rpm asks the
filesystem, dpkg asks its own records.
What this means in practice: dpkg -S wants the canonical path. When a
lookup comes back empty on a Debian-family system, resolve the path first rather
than concluding the file is unpackaged.
dpkg -S "$(realpath /bin/ls)"
2. /tmp and /run are not places to keep things
/run is a tmpfs and is empty after every boot. That is its purpose: it holds
runtime state such as PID files and sockets that should not survive a restart.
/tmp may also be cleared, either at boot or on a timer, and which of those
happens depends on the distribution.
The failure is slow. A service writes state to one of them, works for as long as
the machine stays up, and loses everything at the next reboot. Durable service
data belongs in /var/lib/<service>, and that is what packages use.
3. /proc and /sys are not files, so normal tools mislead you
ls -l /proc/cpuinfo reports a size of zero, and every file under /proc and
/sys reports zero. The content does not exist until you read it, at which point
the kernel generates it. So du reports nothing, backup tools either skip these
trees or hang on them, and a file size tells you nothing about whether there is
content.
Read them with cat, and exclude them from anything that walks the filesystem.
4. Confusing /usr/local with /usr
/usr belongs to the package manager. /usr/local belongs to you, and package
managers do not write there. Software you compile from source installs to
/usr/local by default for exactly this reason: it keeps hand-built binaries
out of the tree that a package upgrade will overwrite.
If you put something in /usr/bin by hand, the next package upgrade that ships
that path will overwrite it without asking. That is not a bug; you wrote into
someone else’s directory.
Work it through
You inherit a Linux server with no documentation. A monitoring check is failing
with a message about a missing binary at /usr/bin/healthcheck. You need to know
whether that file was installed by a package, dropped there by hand, or never
existed.
Reason it out before reading on.
Establish which family you are on, because it decides which query you can run:
. /etc/os-release && echo "$ID ${ID_LIKE:-}"
Say it reports debian. That means dpkg, not rpm.
Ask whether the path exists at all, and if it does, resolve it:
ls -l /usr/bin/healthcheck
Ask the package database who owns it. On the dpkg family, give it the canonical path, because of trip-up one:
dpkg -S "$(realpath /usr/bin/healthcheck 2>/dev/null || echo /usr/bin/healthcheck)"
Three outcomes and what each one tells you:
- A package name comes back. The file belongs to a package and something removed or replaced it. Reinstalling that package restores it.
no path found matching pattern, and the file exists. Somebody put it there by hand. It is not managed, it will not be upgraded, and it will not be reinstalled by anything. It also belongs in/usr/local/bin, not/usr/bin.- The file does not exist and no package claims it. The check is pointing at something that was never installed on this machine. The bug is in the monitoring configuration, not on the server.
The reasoning that matters: you did not guess, and you did not run a command that only works on the other family. You read the system, chose the query that matches it, and let the outcome narrow the possibilities.
Try it
Optional, and only worth doing if you have a VM or a container to hand.
Start from a Debian-family system and reproduce the usr-merge disagreement deliberately.
- Confirm
/binis a symlink and that/bin/lsand/usr/bin/lsshare an inode. - Run
dpkg -S /bin/lsand confirm it fails. - Make it succeed without changing anything on disk.
For step 3 you need the same idea as trip-up one: the database stores canonical paths, so the query has to supply one.
Verification step. You have it right when the same file produces a package name through one spelling and an error through the other, and you can say in one sentence why, without using the word “bug”.
Check yourself
A file must survive a reboot and is service state rather than configuration. Which directory does it belong in, and which two are the tempting wrong answers?
/var/lib/<service>. That is where packages put exactly this kind of thing:
data a service owns and needs to still be there tomorrow.
The tempting wrong answers are /tmp and /run, because both are writable and
convenient and neither complains. /run is emptied on every boot by design, and
/tmp may be cleared on boot or on a timer depending on the distribution.
/etc is the third near-miss. It survives reboots perfectly well, but it is for
configuration you edit, not for state a service writes, and mixing the two makes
backups and version control unpleasant.
ls -l /proc/meminfo reports a size of 0 bytes. Is the file empty?
No. It is not a file in the usual sense at all.
Everything under /proc is generated by the kernel at the moment you read it,
so there is nothing sitting on a disk to have a size. The kernel reports zero
because reporting anything else would be a guess.
Read it with cat and you get plenty of content. The practical consequences:
du sees nothing there, and backup tools should skip /proc and /sys
entirely, because walking them is at best pointless and at worst a hang.
rpm is not installed and dpkg -S returns nothing for a path you can see with ls. Name two explanations and the command that tells them apart.
Either the file genuinely is not owned by any package, meaning somebody put it
there by hand, or you gave dpkg a path it does not have on file, because the
database records the canonical /usr/... spelling and you handed it a
usr-merge symlink.
dpkg -S "$(realpath /path/to/file)" distinguishes them. Resolve the path first;
if it now returns a package, you had spelling, not an unmanaged file. If it still
returns nothing, the file is genuinely unmanaged, which also means nothing will
ever upgrade or reinstall it.
What does ID_LIKE in /etc/os-release give you that ID does not?
ID tells you the specific product: ubuntu, almalinux, opensuse-leap.
ID_LIKE tells you the family it descends from, so Ubuntu reports
ID_LIKE=debian and AlmaLinux reports ID_LIKE="rhel centos fedora".
That is the difference between a script that works on Ubuntu and a script that
works on anything Debian-shaped. Match on ID_LIKE when you care about which
package manager exists, and on ID only when you genuinely need one specific
distribution.
Worth knowing the edge: Debian itself has no ID_LIKE, because it is not like
anything else, it is the thing others are like. A check that only reads
ID_LIKE will miss Debian entirely.
References
- Filesystem Hierarchy Standard, version 3.0 - Linux Foundation. Accessed 2026-08-07.
- hier(7): description of the filesystem hierarchy - Linux man-pages project. Accessed 2026-08-07.
- file-hierarchy(7): systemd’s file system hierarchy overview - Linux man-pages project. Accessed 2026-08-07.
- os-release(5) - Linux man-pages project. Accessed 2026-08-07.
- UsrMerge - Debian Wiki. Accessed 2026-08-07.
- Features/UsrMove - Fedora Project. Accessed 2026-08-07.
- dpkg-query(1) - Linux man-pages project. Accessed 2026-08-07.
- rpm(8) - Linux man-pages project. Accessed 2026-08-07.
- What is Copyleft? - GNU Project. Accessed 2026-08-07.
- SLES Storage Administration Guide: default file systems - SUSE. Accessed 2026-08-09.
- Changes/BtrfsByDefault, the Fedora 33 desktop change and its exclusions - Fedora Project. Accessed 2026-08-09.
- Wayland Architecture - freedesktop.org. Accessed 2026-08-07.
Every block above with a distribution and architecture header was captured by running the command on a Debian 13 (trixie) container, an AlmaLinux 10.2 container, an Ubuntu 24.04 LTS container and an openSUSE Leap 16.0 container. Blocks without one are illustrative.