Module 1: The Terminal and the File System

6. The filesystem as a tree: absolute and relative paths

Description

By the end of this lesson you will be able to locate any file or folder on your system with an absolute path or a relative one, choose with judgment which of the two fits each situation, and read without hesitation what the shortcuts ., .., ~, and - mean the next time they show up in a command, a tutorial, or an error message.

This is not an operating-system technicality: it is the number-one cause of a kind of bug that looks impossible until you understand it. A project works perfectly on your computer, and when you push it to a server or a continuous integration pipeline it explodes with No such file or directory — for a file you swear exists, because you are looking right at it in your folder. The cause, almost always, is a difference in how your system and the server handle case within a path, something you will be able to diagnose in minutes after this lesson.

Connection to the module: the previous lesson left you able to learn any command on your own, by reading its manual. Today we install the map everything else rests on: how the terrain those commands travel is organized. You are not going to make extensive use of pwd, ls, or cd yet — that is exactly the next lesson's topic — today you will use them only as much as needed, as a supporting tool, to see paths resolve in practice.

Everything descends from the same ancestor

Think about a family tree. It does not matter how many branches a family has, or how many different last names show up generations later: if you trace the line back far enough, every descendant connects to the same common ancestor. To locate any person in that tree you do not need to memorize where they live — it is enough to describe their exact lineage: child of, grandchild of, great-grandchild of, all the way to that shared root.

The Linux and macOS filesystem works exactly like that. There is a single starting point, written as a single slash: /. It is called the root, and absolutely everything else — every folder, every file, every external disk you connect, every network drive you mount — descends from that same point. There is no second root running in parallel. When you plug in a USB drive, the system does not open a new tree for it: it grafts it in as one more branch inside the same tree, in a folder that already existed (/media/dev/USB on many Linux distributions, /Volumes/USB on macOS). This contrasts with Windows, where each disk is literally a separate tree with its own letter (C:\, D:\) — the reason why, if you have ever used Windows, the idea of "a single root for everything" can feel strange at first.

The same way you describe a person as "child of" or "grandchild of," you are going to use that same kinship vocabulary to describe folders: every folder (except the root itself) has exactly one parent folder containing it, and can have zero, one, or many child folders or files inside. A path is nothing more than that line of descent written out as text, with a slash separating each generation.

Worked example

pwd

What to expect (the real value depends on your username; dev is used here as an example on Linux):

/home/dev

Read that output as a complete lineage: starting from the root /, there is a folder home, and inside home there is a folder dev — your home directory. Every slash separating a segment marks a jump from one generation to the next: / is the root, home is the root's child, dev is home's child. pwd (print working directory) simply tells you the exact point in that tree you are standing at right now — the command's full name already tells you everything: "print the current working directory."

The typical hierarchy: what each folder stores and why it exists

On any Linux installation you are going to find, hanging directly off the root, a handful of folders with short, cryptic names dating back to the 1970s that the entire industry still uses because changing such a widespread convention would cost more than it is worth. It is worth recognizing them by heart, even if you never need to touch most of them:

  • /home — the personal directories of every user on the system. Yours lives at /home/your-username.
  • /etc — configuration for the whole system: plain-text files that define how services, users, and networking behave. The name comes from "et cetera" — on the earliest Unix systems it was, literally, the folder where "everything else" that did not fit any other category got stored, and over time it became the standard for configuration.
  • /var — data that changes ("variable") while the system runs: activity records (logs), mail queues, caches. If something writes to it on its own, constantly, while you use the machine, it probably lives here.
  • /usr — most of the system's programs and libraries, not user directories. It is a classic naming trap: on older Unix systems, /usr really did store users' personal directories on a second disk; as the system grew, those personal directories moved to /home and /usr stayed reserved for software — but the name never changed. If you ever assume your personal folder lives under /usr because the name "sounds like user," you are going to look in the wrong place.
  • /tmp — temporary files. Any program can write data here that it does not need to keep; many distributions clear it automatically on reboot, so never store anything important there.
  • /opt — third-party software installed in a self-contained way, each program in its own subfolder (/opt/program-name), separate from the software the system ships with by default.

Worked example

ls /

What to expect (the exact list varies by distribution; this is representative of a typical Linux system):

bin  boot  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

That is literally the first generation of the root's descendants: every name you see there is a folder directly a child of /. ls (list) shows a folder's contents — here we use it with no options, just to see those names; reading this output column by column, with all its options, is next lesson's job.

macOS inherits this same base — underneath, it is still a Unix system — but the layer you see as a user changes two key names: your personal directory lives at /Users/your-username, not /home, and the applications you install with a graphical interface live at /Applications, a folder Linux does not even have (on Linux, programs are spread across /usr, /opt, and other places, managed by a package manager instead of all living together in one "applications" folder). If you type ls / on a Mac you are still going to see etc, var, tmp, and usr — they are there, working exactly the same underneath — but you are also going to see Users and Applications, which do not exist on a standard Linux system.

Your home: the one spot in the tree that is completely yours

Your personal directory — /home/your-username on Linux, /Users/your-username on macOS — is the only folder in the entire tree over which you have full control without asking anyone's permission. Almost all your daily work happens there: your projects, your downloads, your documents. Folders like /etc or /usr are owned by the system (technically, by the root user) and modifying them without special authorization is going to run you into a Permission denied — the full permissions and sudo model is module 4's topic, but for now it is enough to know why almost no tutorial asks you to create a project outside your home: it is the only ground where you can write freely from day one.

Absolute path or relative path: which to use and when

Now that you have the full map, there are two different languages for describing where a file lives within that tree — and choosing the right one is not a matter of taste, there is a concrete rule behind it.

An absolute path describes the full lineage from the root: it always starts with /, and it is valid no matter which folder you are standing in when you write it — /home/dev/projects/report.csv means exactly the same thing no matter where you write it from.

A relative path describes the path from where you are standing right now (your current working directory), and that is why it never starts with /projects/report.csv only points to the right file if your current directory is /home/dev; written from anywhere else, it points to something different or to nothing at all.

The rule for choosing: use an absolute path when the result has to work no matter where it runs from — a script that runs automatically (a scheduled task), a configuration file for another program, a command you paste into a team chat for someone else to run on their own machine. Use a relative path when you are working interactively inside a project and the file is near where you are standing: it is shorter to type, and — the reason that actually matters — it keeps working if you move the entire project to another folder or another computer, because it describes a file's position relative to another file within the same project, not relative to the disk's root.

Worked example

pwd
cd projects/reports
pwd
cd /var/log
pwd

What to expect:

/home/dev
/home/dev/projects/reports
/var/log

The first cd uses a relative path (projects/reports, no leading slash): it works because, standing in /home/dev, there is a folder projects with a folder reports inside it — the same line written from any other directory on the system would probably fail with No such file or directory, because there that relative path points somewhere else or nowhere. The second cd uses an absolute path (/var/log, with a leading slash): it would have worked exactly the same no matter which folder you were standing in before typing it, because it describes the full path from the root. Using cd here is just a vehicle for seeing the difference in action — the command in depth, with all its variants, is what you practice in the next lesson.

The shortcuts you are going to type a hundred times a day: ., .., ~, and -

Writing out the full lineage every time would be exhausting, so the shell recognizes four abbreviated symbols. The first three are part of the general path language: you can use them inside any command that takes a path as an argument (cd, ls, cp, cat, whichever). The fourth is different, and it is worth noting why.

  • . means "this very folder." You rarely need it alone, but it shows up all the time as a prefix — ./script.sh explicitly tells the shell "run the script that is right here," something necessary because, for security reasons, the current folder normally is not part of the PATH the shell checks when looking up commands (you will see PATH in depth in module 4).
  • .. means "the parent folder" — a jump up the lineage. ../data/file.csv means "go up one level from where I am, and from there enter data/file.csv." You can chain them: ../../ goes up two levels.
  • ~ is a shortcut for your entire home directory. The shell expands it automatically before running the command: typing ~/projects is exactly the same as typing /home/dev/projects if your username is dev. It is the same value stored in the environment variable $HOME.
  • - means "the directory you were in before your last cd" — but, unlike the previous three, this one is not a generic path symbol: it is a convention that specifically the cd command recognizes (by reading a variable it keeps internally, $OLDPWD), not the filesystem or the shell in general. Typing cat - or ls - is not going to take you anywhere — for most commands, a lone dash has a completely different meaning, or none. How to use cd - to jump between two working directories is exactly what you practice in the next lesson.

Worked example

echo "Your home according to ~: $(echo ~)"
echo "Your home according to \$HOME: $HOME"

What to expect:

Your home according to ~: /home/dev
Your home according to $HOME: /home/dev

Both lines print exactly the same value, because they are two different ways of reaching the same data: ~ is a shortcut the shell expands on its own before running any command, while $HOME is an environment variable that stores that same value explicitly. This confirms something important: ~ is not magic, nor a hidden special case in the filesystem — it is simply text your shell replaces with your home's full path before the command even runs.

The case-sensitivity trap: why "it works on my machine" is not enough

This is probably the difference between systems that produces the most real bugs and gets taught the least. Linux is case-sensitive for file names: Notes.txt, notes.txt, and NOTES.txt are three completely different files and can coexist in the same folder without conflict. macOS, in its factory configuration, is not: its default disk format (APFS) is case-insensitive — but it preserves the case you typed when you created the file. That combination is what sets a silent trap.

Worked example

Suppose on your Mac you create a file with an initial capital letter and then read it back in lowercase:

touch Config.yaml
cat config.yaml

What to expect on macOS (default format, case-insensitive):

(the file's content shows up with no error)

What to expect on Linux, with exactly the same two commands:

cat: config.yaml: No such file or directory

On your Mac, config.yaml and Config.yaml point to the same physical file — the filesystem ignores the case difference when comparing names, even though ls still shows you Config.yaml, with the capitalization you actually typed when creating it: that is what "preserving" case without being sensitive to it means. On Linux, they are two different names, and only one of the two exists. This is exactly the scenario behind a classic bug: you push a project that references config.yaml in the code but the real file in the repository is named Config.yaml — or the other way around — everything runs fine on your Mac laptop for weeks, and the same code fails on the production server or in the continuous integration pipeline, almost always running Linux, with a No such file or directory that makes no sense at first glance because "the file is right there, I can see it."

The practical lesson: never trust that your Mac is going to warn you about a case-sensitivity slip — treat every file name, in any project that is going to run on a server, with the same discipline Linux would demand from day one.

Hidden files: the leading dot is not magic

Any file or folder whose name starts with a dot — .bashrc, .gitignore, .ssh — is a hidden file: ls with no options does not show it, and your operating system's file explorer does not either, by default. It is tempting to think that dot gives the file some kind of special permission or extra protection. It does not, and it is worth being clear on that: a hidden file has exactly the same permissions, the same owner, and the same behavior as any other file. The only thing that changes is that some program — ls, the graphical explorer — decided, by convention, not to show it unless you explicitly ask.

The origin of that convention is purely practical. Every directory actually contains two internal entries the system creates automatically, ones you already know: . (the folder itself) and .. (its parent). If ls always showed them, every listing would start with two useless, repeated lines. The solution adopted decades ago was to hide, by default, any name starting with a dot — and once that rule existed, the community deliberately reused it for their own personal configuration files, the ones you do not want showing up constantly in the middle of your documents.

Worked example

touch .my-notes
ls
ls -a

What to expect:

(ls with no options does not show .my-notes at all)
.  ..  .my-notes  (the rest of your normal files)

ls with no options filters out .my-notes for the sole reason that it starts with a dot — not because it has different permissions or because it is "protected." The -a option (all) turns that filter off and shows absolutely everything, including the two entries . and .. mentioned above. The rest of ls's options — and a full explanation of what each column in ls -l means — is exactly next lesson's topic.

Common mistakes

"A hidden file is protected or more secure" (conceptual). What happens: the student stores a password or a key inside a file named .secret and assumes that, by starting with a dot, no one else can read or modify it. Why it happens: the leading dot only affects whether ls shows it by default; it does not touch the file's permissions at all — a hidden file with open permissions is just as readable to another account on the system as a visible file with the same permissions. How to spot it: run ls -la (you will see this option in depth in the next lesson) on the file and check its permissions column, exactly as you would with any other file — the dot changes nothing about that column. How to fix it: treat any hidden file storing sensitive data with the same permissions discipline as any other sensitive file; the dot in the name does not replace that discipline, it just keeps it out of sight by default.

"/usr is where my user folder lives" (conceptual). What happens: guided by the name, the student looks for their personal directory inside /usr, or writes a script that assumes a user's home is under /usr/username. Why it happens: the name is a historical leftover — on older Unix systems, /usr really did store personal directories on a second disk; today it stores system programs and libraries, not user folders, but the name never got updated to reflect that change. How to spot it: if a command or script assumes a path under /usr for something that "sounds like" the user's personal data and fails with No such file or directory, suspect this naming confusion first. How to fix it: always use ~ or $HOME to refer to a user's personal directory — never build that path by hand assuming /usr or any other name that "sounds" right.

"It works on my Mac, so the file is named correctly" (troubleshooting). What happens: the student names a file with different capitalization than how it is referenced in their code — Data.csv on disk, data.csv in the script — everything runs perfectly on their Mac for days or weeks, and when deploying to a Linux server or running a test in a Docker container (almost always Linux-based) it explodes with No such file or directory for a file that "exists, I can see it right there." Why it happens: macOS's default disk format ignores case differences when comparing names; Linux's does not — two names your Mac treats as the same file are, for Linux, two different files, and only one of them really exists. How to spot it: if a No such file or directory error shows up only on the server or in continuous integration and never locally, compare the exact name in the code against the exact name on disk, character by character — do not trust that "they look the same" at a glance. How to fix it: match the exact capitalization between the file's name and every reference to it in your code, and adopt a fixed convention (for example, always lowercase) for any file that is going to travel to a server.

Exercises

1. Your pwd shows /home/dev/projects/blog. Inside that folder there is a subfolder drafts, and one level up, in /home/dev/projects, there is another folder called shared-assets. Write a relative path and an absolute path that both point to shared-assets from where you are standing.

See solution

Relative path: ../shared-assets.. goes up one level, from blog to projects, and from there enters shared-assets. Absolute path: /home/dev/projects/shared-assets — the full lineage from the root, valid no matter which folder you are standing in when you write it. This works because both describe exactly the same destination in the tree; the only difference is the starting point the path is counted from: one from your current location, the other from /.

2. With $HOME equal to /home/dev and your pwd showing /home/dev/projects/blog/drafts, write the full absolute path each of these shortcuts expands to: ~, .., ../...

See solution

~ expands to /home/dev — your entire home, no matter where you are standing. .. expands to /home/dev/projects/blog — one level above your current location. ../.. expands to /home/dev/projects — two levels up, one for each chained ... This works because every .. is an independent jump to the immediate parent: chaining them simply repeats that jump as many times as you write .., while ~ does not depend at all on where you are standing, because it always points to the same fixed destination.

3. You are writing two things: (a) a scheduled task that runs automatically every night and needs to read a file from your project, and (b) a command you are typing right now in your terminal, standing inside that same project, to copy a file to a neighboring folder. Would you use an absolute or a relative path in each case, and why?

See solution

For the scheduled task (a), an absolute path. An automated task does not guarantee which directory it runs from — that depends on how the system scheduled it, not on where you were standing when you wrote it — so only an absolute path works reliably no matter the context it runs in. For the interactive command (b), a relative path: it is shorter to type, and since you already know exactly where you are standing at that moment, there is no ambiguity — plus, if you ever move the entire project to another folder, that relative path between files in the same project stays valid, while a hand-written absolute path would stop being so. This works because the rule is not "one is better than the other," but that each solves a different problem: the absolute one guarantees the same result no matter the starting point; the relative one is portable along with the project it describes.

4. On your Mac you have a file named Logo.png inside assets/, and your HTML code references it as assets/logo.png (lowercase). Everything looks perfect when you open the page locally. You push the project to a Linux server and the logo does not load; in the browser console you see a 404 error for that file. Why does it work in one place and not the other, and how do you fix it permanently, without depending on remembering next time?

See solution

It works on your Mac because, by default, macOS is case-insensitive when comparing file names: Logo.png and logo.png point to the same physical file as far as the system is concerned, even though ls still shows Logo.png with the capitalization you actually typed. On the Linux server, Logo.png and logo.png are two different names, and only the first one really exists on disk — the browser requests assets/logo.png, that file does not exist under that exact name, and the server responds 404. To fix it permanently, renaming the file once is not enough: it is worth adopting a fixed convention for the whole project — for example, always lowercase for file names — and matching every reference in the code to the exact name on disk, letter for letter. This works because the underlying problem is not one particular badly named file, but the habit of not treating case as significant — a habit your Mac lets you keep without penalty, until the same code runs on a system that does distinguish it.

Summary and next step

Today you installed the map: the filesystem is a single tree rooted at /, with a typical hierarchy (/home, /etc, /var, /usr, /tmp, /opt on Linux; /Users and /Applications as macOS's visible differences) and your home as the only ground that is completely yours within that tree. You saw the difference between absolute and relative paths, with a concrete rule for choosing between them, and what each of the shortcuts ., .., ~, and - expands to — and why the last one is different from the other three. You also saw two traps that look trivial and are not: that case within a path can completely change the result between your Mac and a Linux server, and that a hidden file has no special permissions at all — it is just out of sight by default.

Before moving on you should be able to: explain from memory why the filesystem is "a tree with a single root"; given any pwd, write both an absolute path and a relative path to a nearby file; say without hesitation what each of ., .., ~, and - expands to; and explain in your own words why a project can work on your Mac and break when deployed on Linux because of a case-sensitivity issue.

You now have the full language for describing where anything lives in the tree. What you are still missing are the commands that actually move you around it and show you, with precision, what is at every point where you stop — pwd as a habit for orienting yourself before acting, ls read column by column, and cd with all its variants, including that cd - we only mentioned today. That is exactly the next lesson's topic.

Resources