Home / Alt manpages / readlink(1)

  • readlink(1)
  • User command
  • linux

Resolve Symlinks Safely with readlink

You will finish with a reliable way to inspect a symbolic link, turn a path into its canonical form, and choose the right behaviour when part of that path does not exist. The examples use GNU readlink from coreutils 9.4, installed on this machine.

Allow about ten minutes. You need a shell and a path you can read. The checks below are ordinary user commands. They do not need sudo, and they do not change the link or its target. Be careful with shell redirection: the commands shown read paths and print results, rather than overwriting files.

1. Check the installed command

Confirm which executable will run and record its version before relying on option details in a script:

$ command -v readlink
/usr/bin/readlink
$ readlink --version | head -n 1
readlink (GNU coreutils) 9.4

The command accepts one or more file names. With no canonicalisation option, it prints the value stored in a symbolic link. It does not follow the link merely to report its target.

Checkpoint: if command -v reports an unexpected path, stop and inspect that installation before comparing output with this guide. A shell alias or a different implementation can have different options.

Pass the link itself, not the file you expect it to reach:

$ readlink /path/to/current-link
../releases/2026-09-26

The output is the link's stored value. It may be relative, and it may name a path that no longer exists. That is useful when auditing a deployment link or diagnosing a broken target. A successful exit status means that readlink read the link entry; it does not prove that the destination is usable.

For a path supplied by a variable, quote it so whitespace is not split into separate arguments:

$ link_path='/path/to/current-link'
$ readlink -- "$link_path"
../releases/2026-09-26

The -- marks the end of options. It is a useful boundary when a file name might begin with a hyphen.

3. Canonicalise a path that exists

Use --canonicalize-existing, or its short form -e, when every component must exist:

$ readlink -e /path/to/current-link/config/app.conf
/srv/releases/2026-09-26/config/app.conf

This follows symbolic links in every component, resolves relative path elements, and prints an absolute canonical name. If the final file or any directory in the path is missing, the command returns non-zero and normally prints no result. That strict failure is useful before opening a configuration file or handing a path to another program.

Verify the status immediately if a script needs to distinguish success from an empty result:

$ readlink -e /path/to/current-link/config/app.conf > /tmp/canonical-path
$ status=$?
$ printf 'readlink status: %s\n' "$status"
readlink status: 0

The temporary file in this example is only an output capture. Check it before consuming it:

$ test -s /tmp/canonical-path && cat /tmp/canonical-path
/srv/releases/2026-09-26/config/app.conf

4. Allow a missing final component

Use --canonicalize, or -f, when the parent directories must exist but the final component may be absent:

$ readlink -f /path/to/current-link/cache/new-index
/srv/releases/2026-09-26/cache/new-index

Every symlink is followed. All components except the last must exist, so this form is useful for calculating where a new file would belong before creating it. It does not create new-index, its parent directory, or any symlink.

Do not confuse a resolved name with a safe write operation. If another command later writes that path, check its permissions and whether replacing an existing file is acceptable. This guide does not create or replace anything, so there is no undo step for the examples.

5. Resolve paths even when components are missing

Use --canonicalize-missing, or -m, when no component needs to exist:

$ readlink -m /path/to/not-yet-created/../cache/index
/path/to/cache/index

This still follows symlinks that can be followed and normalises the path, but it does not require existing files or directories. It is convenient for displaying a planned destination or comparing names before a later provisioning step.

There is a safety boundary here. A path that -m can resolve is only a name. It is not evidence that the parent is present, writable, mounted where you expect, or protected from a race. Recheck the filesystem immediately before a security-sensitive operation, and use the receiving program's safe file-opening mechanism where one exists.

6. Choose output delimiters for scripts

By default, each result ends with a newline. -n removes that final newline for a single value:

$ target=$(readlink -n /path/to/current-link)
$ printf 'target=[%s]\n' "$target"
target=[../releases/2026-09-26]

Command substitution already removes trailing newlines, so -n is most visible when piping directly to another program or when preserving exact bytes matters.

For multiple names, use -z to separate results with NUL characters instead of newlines. That handles names containing embedded newlines:

$ readlink -z /path/to/link-one /path/to/link-two | od -An -t x1
 2e 2e 2f 72 65 6c 65 61 73 65 73 2f 6f 6e 65 00
 2e 2e 2f 72 65 6c 65 61 73 65 73 2f 74 77 6f 00

The final 00 on each record is the NUL delimiter. Pair this format with a NUL-aware consumer. Do not pipe it to a line-oriented parser and assume the records are intact.

7. Diagnose a failed lookup

If a strict canonicalisation fails, first inspect the path without changing it:

$ ls -ld /path/to/current-link /path/to/current-link/config
$ test -r /path/to/current-link/config/app.conf && echo readable

Then choose the mode that matches the real requirement. Use -e when a complete existing path is mandatory, -f when only the final component may be new, and -m when missing parents are expected. A non-zero result from -e is not fixed by adding sudo if the file simply does not exist.

Use -v or --verbose when you need error messages. The manual also lists -q and -s for suppressing most diagnostics. Keep diagnostics enabled during troubleshooting; silence is appropriate only when the caller records and handles the exit status itself.

Never use readlink as proof that a path is safe to access. It reports names. It does not reserve the result, prevent a symlink from changing afterwards, or enforce permissions. For a privileged service, resolve and open files using a design that addresses those race conditions, and review the operation before granting elevated access.

Done means

  • You confirmed that GNU coreutils 9.4 is the installed implementation.
  • You used plain readlink to inspect a link's stored target.
  • You chose -e, -f, or -m according to which components may be missing.
  • You checked the exit status instead of treating printed text as proof of success.
  • You used -z when newline-delimited output could be ambiguous.
  • You treated a canonical path as a name to inspect, not as a security guarantee.