Resolve Paths Safely with realpath
A script breaks the moment it meets a relative path, a stray .. or a symlink, and GNU realpath exists to make that whole class of bug go away. It turns any pathname into an absolute, normalised name and gives you a clean choice for what happens when a component is missing. This guide uses GNU coreutils 9.4, from Ubuntu package version 9.4-3ubuntu6.3.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about ten minutes. You need a shell and a path you can read. The examples only inspect names and print results: they do not create, remove or modify files, and none of them need elevated privileges.
1. Check the installed command
Confirm which executable the shell will run, then record its version:
$ command -v realpath
/usr/bin/realpath
$ realpath --version
realpath (GNU coreutils) 9.4
Checkpoint
If command -v prints nothing, realpath is not available through your current PATH. Do not replace it with an unverified script that happens to share the name; check your distribution's coreutils package instead.
2. Resolve an ordinary path
Pass one or more pathnames as arguments. By default, every component except the last one must exist, and the default physical mode expands symlinks as it encounters them:
$ realpath /var/log/../log/syslog
/var/log/syslog
The result goes to standard output, one resolved name per input, and the command never touches the path on disk. A successful result is a useful value to hand to another command, but it is not proof that the last component is a regular file: it may just as well be a directory, a device, or another object the filesystem allows.
Use the status immediately after a check if a script needs to distinguish success from failure:
$ realpath /var/log/syslog
/var/log/syslog
$ printf '%s\n' "$?"
0
3. Choose how missing components are handled
The default is a middle ground: parent components must exist, but the final component can be absent. Two flags let you pick a stricter or looser rule on purpose.
Use --canonicalize-existing when every component, including the last one, must exist:
$ realpath --canonicalize-existing /var/log/syslog
/var/log/syslog
$ realpath --canonicalize-existing /path/that/does/not/exist
realpath: /path/that/does/not/exist: No such file or directory
The second command returns non-zero. In a script, do not use its printed error text as the test; branch on the exit status instead and decide deliberately whether a missing path is expected.
Use --canonicalize-missing when you are preparing a name before creating it, or when no component is required to exist:
$ realpath --canonicalize-missing /tmp/example/new/../result.txt
/tmp/example/result.txt
This option only calculates a name: it does not create /tmp/example or result.txt. If a later command creates the file, its permissions and race conditions are that command's problem, not realpath's.
4. See the symlink boundary
This is the part that bites people: physical resolution is the default, but logical resolution changes what happens when .. is interpreted relative to a symlinked directory. Build a small test tree in a scratch directory to see it for yourself:
$ work=/tmp/realpath-demo
$ mkdir -p "$work/real/child"
$ ln -s "$work/real" "$work/link"
$ realpath --physical "$work/link/child/../child"
/tmp/realpath-demo/real/child
$ realpath --logical "$work/link/child/../child"
/tmp/realpath-demo/link/child
The physical result follows link before resolving the parent reference. Logical mode resolves the .. against the written path first, so the symlink stays in the result. The difference matters whenever a path crosses a symlinked deployment directory. Without a deliberate need for logical paths, the default physical behaviour is usually the less surprising choice.
Warning
The mkdir and ln lines above change state in /tmp. Use a unique scratch directory, and do not point them at a directory containing useful data. If you want to undo this particular test after checking it, remove only that scratch directory with rm -rf -- /tmp/realpath-demo. Review the path before running a recursive removal.
5. Produce a relative name or retain symlinks
Three options here solve three different annoyances.
--relative-to=DIR resolves the input and prints its name relative to the supplied directory:
$ realpath --relative-to=/var /var/log/syslog
log/syslog
The base directory is an argument to the calculation, not a directory change: both the base and the input get resolved for the comparison, and if the input lands outside the base, the output can contain ...
--relative-base=DIR is for when you want compact names only for paths below a known tree. Paths below the base print relative to it; everything else stays absolute:
$ realpath --relative-base=/var/log /var/log/syslog /etc/hosts
syslog
/etc/hosts
--strip, also spelled --no-symlinks, gives normalised absolute syntax without expanding symlink components:
$ realpath --strip /var/log/../log/syslog
/var/log/syslog
Do not confuse this with checking the physical destination: a stripped result can still name a symlink, so use the default or --physical when the resolved target is what actually matters.
6. Make scripts quiet and binary-safe
--quiet suppresses most diagnostics, handy when a script wants to report its own error message. It does not make an invalid path successful:
if resolved=$(realpath --quiet --canonicalize-existing -- "$input"); then
printf 'Using %s\n' "$resolved"
else
status=$?
printf 'Cannot resolve input (status %s)\n' "$status" >&2
exit "$status"
fi
The -- marks the end of options, so a filename beginning with a hyphen is treated as a pathname rather than a flag. The example above deliberately uses command substitution for a single result. For multiple arbitrary filenames, reach for --zero and a NUL-aware consumer instead, because ordinary newline-separated output cannot safely represent a newline inside a filename:
$ realpath --zero -- /path/to/one /path/to/two | od -An -t x1
2f 70 61 74 68 2f 74 6f 2f 6f 6e 65 00
Do not parse output with whitespace splitting when filenames may contain spaces, tabs or newlines. And remember that realpath resolves names, it does not grant access: a later open can still fail, and a path can change between resolution and use.
Done means
- You checked that the installed command is GNU coreutils 9.4 or identified the version you are documenting.
- You selected default physical resolution, logical resolution, or symlink stripping deliberately.
- You used
--canonicalize-existingfor strict existence checks and--canonicalize-missingonly when absent components are acceptable. - Your script checks the exit status and uses
--before untrusted or hyphen-leading pathnames. - You use
--zerowith a NUL-aware consumer when filenames cannot safely be separated by newlines.