realpath and readlink: Resolving Absolute Paths and Symlinks

realpath and readlink: Resolving Absolute Paths and Symlinks

What You'll Learn

  • The difference between realpath and readlink, 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 pathrealpath path
  • Want to see what a symlink points toreadlink link
  • Want to follow every link and .. down to one real pathrealpath path or readlink -f path
  • In scripts, use realpath or readlink -f — never bare readlink

Assumptions (target environment)

  • GNU coreutils (Ubuntu / Debian / RHEL and most common Linux distros)
  • realpath ships 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: readlink reads the contents of a symlink (its target string). realpath resolves a whole path into its real absolute form. Add readlink -f and it behaves almost exactly like realpath.

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.

Conclusion: Bare readlink prints the link target as stored. Add -f and it follows links recursively and returns the final absolute path.

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 path returns the absolute path with all symlinks, .., and . resolved. Its default matches readlink -f: all but the last component must exist.

$ 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.

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 readlink fails silently on regular files. For path resolution in scripts, use realpath or readlink -f.

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).

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 path or readlink -f path
  • Guarantee existence → -e
  • Compute a path you're about to create → -f or -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.

Next Reading