Absolute vs Relative Paths
Every file and directory on a Linux system lives somewhere in one single, unified tree that starts at the root directory, written as a lone slash /. A path is nothing more than a set of directions for finding a location in that tree, and there are exactly two ways to write those directions: start from the root every single time (an absolute path), or start from wherever the shell currently happens to be sitting (a relative path). Understanding the difference — and why a script that works perfectly when you run it by hand can fail mysteriously when cron runs it — is one of the most practical skills in daily Linux use.
Overview: How Paths Work
Unlike Windows, which gives every disk its own drive letter (C:, D:), Linux has exactly one filesystem tree. It starts at the root directory /, and every other disk, USB stick, or network share is mounted onto some directory inside that tree rather than getting a letter of its own. Because there is only one tree, every location in it can always be described starting from the root — that description is an absolute path, and it never changes meaning no matter who asks for it or what directory they happen to be standing in. /var/log/syslog means exactly one thing on the system, always.
A relative path, by contrast, does not start with /. It is interpreted relative to the current working directory (cwd) of whichever process is resolving it. The cwd is not a shell fiction — it is real kernel state attached to every running process (readable via the getcwd() system call), and the shell keeps its own copy synchronized in the $PWD environment variable every time you run cd. That is why the exact same text, reports/2026.csv, can point at two completely different files depending on which directory you were in when you typed it.
Two special relative names exist inside literally every directory on the filesystem: . and ... These are not shell tricks — they are real directory entries that the kernel creates and maintains, which is why they show up when you run ls -a. . is a link back to the directory itself; .. is a link to its parent (at the root, .. simply links back to the root, since root has no parent). Every relative path implicitly starts its walk from an invisible ..
The shell adds one more convenience on top of all this: ~ expands to your home directory before the command ever runs. Typing cd ~/projects is shorthand for cd /home/ubuntu/projects — after Bash expands it, it is an absolute path, not a relative one. Similarly, cd - jumps back to whatever directory you were in before your last cd, using the value Bash stored in $OLDPWD. It is also worth knowing that cd is a shell builtin, not a separate program on disk — it has to be, because changing directory means changing the calling shell process’s own working-directory state. An external program can only change the working directory of itself (a child process), which disappears the instant that program exits, so cd as a standalone executable would be useless.
Syntax
There is no special “path command” — paths are simply arguments you hand to commands like cd, ls, cat, or cp. What matters is how the path string itself is written:
| Form | Meaning |
|---|---|
/ |
The root of the filesystem; a path starting with this is absolute. |
name or dir/name |
Relative — resolved against the current working directory. |
. |
The current directory itself. |
.. |
The parent of the current directory. |
~ |
Expanded by Bash to your home directory before the command runs. |
- |
Understood by cd only, meaning “the previous directory” ($OLDPWD). |
In generic form, a command that takes a path looks like this (the angle brackets below are placeholders for a real path, not literal characters you type):
cd "<directory>"
ls "<path>"
cat "<path>"
Examples
1. Absolute paths always mean the same place
Starting from a project directory, moving with an absolute path lands in the exact same place no matter where you started:
pwd
cd /var/log
pwd
Output:
/home/ubuntu/projects/blog
/var/log
It would not matter if the first pwd had printed /tmp or /home/ubuntu instead — cd /var/log always resolves to the same directory, because it starts its lookup at the root every time.
2. Relative paths depend on where you start
pwd
cd ../notes
pwd
ls
Output:
/home/ubuntu/projects/blog
/home/ubuntu/projects/notes
meeting-notes.md todo.md
.. moves up to the parent of blog, which is projects, and then notes steps back down into the sibling directory. The same command, cd ../notes, typed from a completely different starting directory would land somewhere else entirely (or fail with “No such file or directory” if no sibling named notes exists there).
3. Converting a relative path to absolute with realpath
cd ~/projects/blog/drafts
realpath ./draft1.md
Output:
/home/ubuntu/projects/blog/drafts/draft1.md
realpath takes any path — relative or absolute, symlinks and all — and prints its fully resolved, canonical absolute form. This is invaluable inside scripts, where you often need to pin down exactly what a relative path refers to before acting on it.
How Path Resolution Works Step by Step
When any program — the shell, cat, cp, anything — needs to turn a path string into an actual file, the kernel walks it component by component:
- Look at the first character. If it is
/, start the lookup at the root directory’s inode. Otherwise, start at the calling process’s current working directory inode. - Split the rest of the string on
/into components (for example,projects/blog/draftsbecomesprojects, thenblog, thendrafts). - For each component, search the directory-entry table of the current directory for a matching name, which maps that name to an inode number.
- Move into that inode. If it turns out to be a symbolic link, follow it and resume resolution from the link’s target (Linux gives up with an error,
ELOOP, after too many redirects, which is how it detects a symlink loop). - Repeat until the last component has been resolved. The final inode is the file or directory the command actually operates on.
This lookup happens fresh, from scratch, every single time a path is used — the kernel does not cache “the meaning” of a relative path across commands. Tilde expansion and variable expansion (like $HOME) are different: those happen once, in the shell, purely as text substitution, before the resulting string is ever handed to the kernel for the walk described above.
Common Mistakes
Relative paths inside scripts run from an unknown directory
This script works fine when you run it by hand from ~/projects/blog, but cron does not start jobs from your project directory — it typically starts them from your home directory or /:
#!/usr/bin/env bash
tar -czf backup.tar.gz ./data
cp backup.tar.gz backups/
Run from cron, ./data resolves against whatever cron’s working directory happens to be, almost certainly not the folder you meant, and the whole script either archives the wrong thing or fails outright. Anchor the script to its own location instead:
#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
tar -czf "$script_dir/backup.tar.gz" "$script_dir/data"
cp "$script_dir/backup.tar.gz" "$HOME/backups/"
dirname "${BASH_SOURCE[0]}" gets the directory the script file lives in, and wrapping it in cd ... && pwd converts it into a full absolute path, so the script behaves identically no matter what invoked it.
Forgetting that a leading slash changes everything
A very common typo is adding a leading slash out of habit and accidentally jumping to the filesystem root instead of a subdirectory of home:
cd /Documents
This fails, because there is no Documents directory directly under the root — the user almost certainly meant their home directory’s Documents folder:
cd ~/Documents
Leaving a path with spaces unquoted
Bash splits unquoted arguments on whitespace, so a directory name containing a space turns into two separate arguments:
cd Old Files
cd only accepts one argument, so this fails with “too many arguments.” Quoting the whole path keeps it as one word:
cd "Old Files"
Best Practices
- Use absolute paths (or a path resolved with
realpath/$(dirname "$0")) in cron jobs, systemd units, and any script whose working directory you don’t control. - Prefer short relative paths for everyday interactive navigation — they’re faster to type and move naturally with you as you browse a project.
- Always quote path expansions:
"$file","$HOME/backups","$(pwd)"— never bare$file. - Use
cd -to bounce back to the previous directory instead of retyping a long path. - Use tab-completion for anything longer than a couple of components; it eliminates typos in long paths entirely.
- Inside scripts meant to run as different users, expand
$HOMEexplicitly rather than relying on~, since its expansion depends on the shell’s notion of the invoking user’s home directory. - When debugging “file not found” errors, run
pwdfirst — most such errors turn out to be a relative path resolved from the wrong directory, not a missing file.
Practice Exercises
- Create a small tree with
mkdir -p ~/practice/pathdemo/alpha/sub ~/practice/pathdemo/beta/sub. From insidealpha/sub, write one relative-pathcdcommand that lands you inbeta/sub, then confirm withpwd. Now do it again using only an absolute path. - Write a script called
whereami.shthat prints its own absolute location usingdirnameandrealpath, regardless of which directory it is run from. Make it executable and run it from two different directories to prove it prints the same location both times. - Explain, in your own words, why a script that reads
source ./config.shwould break if it were installed as a cron job, and rewrite that line so it works no matter where cron starts it.
Summary
- An absolute path starts at
/and always refers to the same location, no matter which directory the caller is in. - A relative path does not start with
/and is resolved against the current working directory, which is real per-process kernel state, not just a shell convention. .and..are genuine directory entries maintained by the filesystem, meaning “this directory” and “its parent.”~is expanded by Bash into$HOMEbefore a command runs, turning into an absolute path;cd -returns to$OLDPWD.cdmust be a shell builtin, since only the shell itself can change its own working directory.- Use
realpathto convert any relative path into its canonical absolute form. - Scripts run by cron, systemd, or other automation should always use absolute paths, or resolve their own location first — never assume a particular working directory.
