Skip to content
Linux Basics: Resolve Paths and Check Existence with GNU realpath

Linux Basics: Resolve Paths and Check Existence with GNU realpath

October 25, 2026

A path can contain . and .., repeat directory separators, or reach a file through a symbolic link. GNU realpath turns that spelling into a resolved path. Its options also let you distinguish an existing target from a path you are only planning to create.

These examples were tested on Linux with GNU Bash 5.2.37 and GNU coreutils 9.7. The scripts use LC_ALL=C and invented directories. Relative output keeps the results independent of the temporary directory’s random name.

Prepare a scratch directory

You need Bash, GNU realpath, a text editor, and the mkdir, ln, mktemp, rm, and rmdir utilities. Keep using the same terminal. Stop if setup fails; the guards below do that before any example file is saved. Save each named script here and run the scripts once in order.

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

1. Resolve a release alias into a stable relative report

Save this as realpath-target.sh:

#!/usr/bin/env bash
set -eu
export LC_ALL=C
mkdir -p project/releases/v1
printf 'mode=preview\n' > project/releases/v1/settings.txt
ln -s releases/v1 project/current
resolved=$(realpath --canonicalize-existing \
  project/current/./settings.txt)
expected=$(realpath --canonicalize-existing \
  project/releases/v1/settings.txt)
test "$resolved" = "$expected"
printf 'Alias and target resolve equally\n'
printf 'Relative target: '
realpath --canonicalize-existing --relative-to=project \
  project/current/./settings.txt

Run it:

bash realpath-target.sh

Output:

Alias and target resolve equally
Relative target: releases/v1/settings.txt

The first two calls produce absolute paths and compare them without printing the machine-specific prefix. --relative-to makes the final report relative to the named base directory. That base does not have to be your shell’s working directory.

--canonicalize-existing, shortened to -e, requires every component to exist. Choose it explicitly when a missing final file should be an error: GNU realpath’s default mode permits a missing final component. The GNU realpath manual packaged by Debian describes the existence and relative-output options.

2. See why a parent path depends on symlink handling

Save this as realpath-parent.sh:

#!/usr/bin/env bash
set -eu
export LC_ALL=C
mkdir -p layout/releases/v1
ln -s releases/v1 layout/current
printf 'Physical parent: '
realpath -e -P --relative-to=layout layout/current/..
printf 'Logical parent: '
realpath -e -L --relative-to=layout layout/current/..

Run it:

bash realpath-parent.sh

Output:

Physical parent: releases
Logical parent: .

Physical resolution, -P, follows current to releases/v1 before applying .., so its parent is releases. Logical resolution, -L, handles the current/.. pair before following the link, leaving the base directory itself, printed as ..

Physical mode is the default. Pick the mode that matches the question you are asking rather than treating the two results as interchangeable. A text cleanup of path components can change which location a symlink-containing path denotes.

3. Normalize a planned path without creating it

Save this as realpath-missing.sh:

#!/usr/bin/env bash
set -eu
export LC_ALL=C
mkdir planned
candidate='planned/reports/../output/result.txt'
printf 'Planned relative path: '
realpath --canonicalize-missing --relative-to=planned "$candidate"
if realpath --canonicalize-existing "$candidate" \
  > rejected-path.txt 2> missing-path-error.txt; then
  printf 'Unexpected success\n' >&2
  exit 1
else
  status=$?
  test -s missing-path-error.txt
  printf 'Existing-path check rejected: status %s\n' "$status"
fi
test ! -e planned/output
printf 'No output directory was created\n'

Run it:

bash realpath-missing.sh

Output:

Planned relative path: output/result.txt
Existing-path check rejected: status 1
No output directory was created

--canonicalize-missing, or -m, tolerates missing components. Its successful result is a normalized path description, not evidence that the destination exists or is writable. The strict call fails, and the script treats that failure as the expected result of the example.

-m still resolves symlinks it encounters. For purely lexical normalization without expanding symlinks, GNU realpath also provides --no-symlinks (-s), commonly combined with -m. Be explicit about whether your workflow needs a filesystem lookup or only a spelling transformation.

Keep in mind

A resolved string does not lock the filesystem. Another process can replace a directory or link after this check and before a later open. Do not use a realpath prefix comparison alone as a security boundary for untrusted paths.

The examples use simple filenames. For arbitrary filenames, GNU realpath -z terminates results with NUL rather than newline, but the receiving tool must also understand NUL-delimited records. Bash command substitution cannot retain NUL bytes.

Clean up

Remove only the named files and links, then remove the known empty directories from the leaves upward:

rm -- realpath-target.sh realpath-parent.sh realpath-missing.sh \
  project/current project/releases/v1/settings.txt layout/current \
  rejected-path.txt missing-path-error.txt
rmdir -- project/releases/v1 project/releases project \
  layout/releases/v1 layout/releases layout planned
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

Acknowledgements

This post was created with assistance from AI.

Last updated on