realpath and readlink: Resolving Absolute Paths and Symlinks
What You'll Learn
- The difference between
realpathandreadlink, and when to use each - How to resolve symlinks and
..segments into an absolute path - The three existence-check levels behind
-f/-e/-m - How to write safe path resolution in shell scripts
Quick Summary
- Want an absolute path →
realpath path - Want to see what a symlink points to →
readlink link - Want to follow every link and
..down to one real path →realpath pathorreadlink -f path - In scripts, use
realpathorreadlink -f— never barereadlink
Assumptions (target environment)
- GNU coreutils (Ubuntu / Debian / RHEL and most common Linux distros)
realpathships as standard from coreutils 8.15 onward- Behavior in this article verified on coreutils 9.4
What's the difference between realpath and readlink?
Conclusion:
readlinkreads the contents of a symlink (its target string).realpathresolves a whole path into its real absolute form. Addreadlink -fand it behaves almost exactly likerealpath.
The two are close in name and purpose, but their bare behavior differs.
| Aspect | readlink (no options) |
realpath (no options) |
|---|---|---|
| Main purpose | Read the link's target string | Compute the real absolute path |
| Target is not a symlink | Prints nothing, fails | Outputs the absolute path |
| Relative link target | Kept as the stored relative string | Resolved to an absolute path |
Resolving .. |
No | Yes |
The key point: bare readlink is symlink-only. Run it on a regular file or directory and it fails silently (see pitfalls below). realpath, by contrast, returns an absolute path for any path.
How do you use readlink?
Conclusion: Bare
readlinkprints the link target as stored. Add-fand it follows links recursively and returns the final absolute path.
Show the link's contents (no options)
readlink prints the string stored inside a symlink. On most systems /bin is a relative link to usr/bin.
$ readlink /bin
usr/bin
Because the stored value is relative (usr/bin), the output stays relative, and .. is not resolved. Use this when you only want to know what a link points to.
Fully resolve with -f / -e / -m
Add -f (--canonicalize) to follow every symlink along the way, process .., and return an absolute path.
$ readlink -f /bin
/usr/bin
The -f family has three modes that differ in how strict the existence check is.
| Option | Long form | Existence requirement |
|---|---|---|
-f |
--canonicalize |
All but the last component must exist |
-e |
--canonicalize-existing |
All components must exist |
-m |
--canonicalize-missing |
None need exist |
# Parent exists, only the final file is missing → -f succeeds, -e fails $ readlink -f /etc/does-not-exist.conf /etc/does-not-exist.conf $ readlink -e /etc/does-not-exist.conf # prints nothing, exit 1
Use -f or -m to compute the final path of a file you're about to create; use -e when you need to guarantee the path exists.
When showing a link target, -n (--no-newline) suppresses the trailing newline. Command substitution $(...) already strips trailing newlines, so it's rarely needed there, but it helps when concatenating with printf.
How do you use realpath?
Conclusion:
realpath pathreturns the absolute path with all symlinks,.., and.resolved. Its default matchesreadlink -f: all but the last component must exist.
Resolve links and relative paths into one real path
$ realpath /bin
/usr/bin
Unlike readlink, realpath also works on paths that aren't symlinks — that's the big practical difference.
$ cd /var/log $ realpath ../tmp
/var/tmp
.., ., and duplicate slashes are all normalized.
Control the existence check (-e / -m)
realpath uses the same three levels as readlink.
- Default: all but the last component must exist (the parent must be real)
-e(--canonicalize-existing): every component must exist-m(--canonicalize-missing): no component needs to exist
# Parent directory does not exist → fails even by default $ realpath /no-such-dir/file.txt realpath: /no-such-dir/file.txt: No such file or directory # -m normalizes and returns even a fully missing path $ realpath -m /no-such-dir/file.txt /no-such-dir/file.txt
Convert to a relative path with --relative-to
Beyond absolute paths, realpath can also compute a path relative to a base directory.
$ realpath --relative-to=/home /home/alice/work/report.txt
alice/work/report.txt
Handy in scripts that need a position relative to a config directory. --relative-base=DIR keeps the result relative only when the target is under DIR, and absolute otherwise.
Keep links intact with -s
Add -s (--strip / --no-symlinks) to normalize . and .. without resolving symlinks. Use it when you want to tidy a path while preserving the link structure.
$ realpath -s /bin/../bin
/bin
What are the common pitfalls?
Conclusion: The biggest trap is that bare
readlinkfails silently on regular files. For path resolution in scripts, userealpathorreadlink -f.
1. Bare readlink on a regular file returns empty
readlink (no options) prints nothing and exits 1 for anything that isn't a symlink.
$ readlink /etc/hostname # no output, exit 1 (/etc/hostname is a regular file)
Written in a script, this leaves a variable empty when the target isn't a symlink — an easy way to cause surprises.
# Risky: p is empty if $f is not a symlink p=$(readlink "$f") # Safe: p holds an absolute path for symlinks and regular files alike p=$(realpath "$f")
2. Confusing -f and -e
If you want to resolve an output path that doesn't exist yet, -e will fail. For a path you're about to create, choose -f (last component may be missing) or -m (everything may be missing).
3. Trailing slash on a symlink
A trailing / on a symlink points to the directory it targets, not the link itself. This can silently change the resolved result, so omit the trailing / when you want to inspect the link itself.
Rule of thumb
- See the target string one level deep →
readlink link - Need the real absolute path (scripts included) →
realpath pathorreadlink -f path - Guarantee existence →
-e - Compute a path you're about to create →
-for-m
A practical scripting example
Conclusion: Resolving a script's own install directory is the classic use case for
realpath/readlink -f. Even when launched through a symlink, you get the real directory.
Shell scripts often need to reference other files relative to their own location. realpath resolves the real location even when the script is invoked through a symlink.
#!/bin/bash # Resolve the script's real directory script_path=$(realpath "$0") script_dir=$(dirname "$script_path") echo "Running script: $script_path" echo "Install dir: $script_dir" # Safely reference a config file next to the script config="$script_dir/config.env"
On minimal systems without realpath, readlink -f "$0" is the drop-in alternative. Both default to the same "all but the last component must exist" behavior.
BSD-based systems such as macOS ship a readlink without -f (or with different behavior). For portable scripts that can't assume GNU coreutils, check whether realpath exists, or fall back to a cd "$(dirname "$0")" && pwd idiom.