Python os.readlink Function

Complete guide to Python's os.readlink function covering symbolic link resolution, path handling, and practical examples.

Python os.readlink Function

Python os.readlink Function

Last modified April 11, 2025

This comprehensive guide explores Python’s os.readlink function, which reads the target of symbolic links. We’ll cover path resolution, error handling, and practical filesystem navigation examples.

Basic Definitions

The os.readlink function returns a string representing the path to which the symbolic link points. It only works on symbolic links.

Key parameters: path (symbolic link to read). Returns the link target path. Raises OSError if path isn’t a symbolic link or other errors occur.

The simplest use of os.readlink reads the target of a symbolic link. First we create a link, then read its target.

basic_readlink.py

import os

Create a symbolic link

target = “original.txt” link_name = “symlink.txt”

with open(target, “w”) as f: f.write(“Test content”)

os.symlink(target, link_name)

Read the symbolic link

try: link_target = os.readlink(link_name) print(f"’{link_name}’ points to ‘{link_target}’") except OSError as e: print(f"Error reading link: {e}")

Clean up

os.remove(link_name) os.remove(target)

This example creates a file and symbolic link, then reads the link target. The try/except block handles cases where the path isn’t a symbolic link.

Note that os.readlink returns the raw link contents, which may be relative or absolute depending on how the link was created.

Symbolic links can contain relative paths. os.readlink returns the exact stored path without resolution. This example demonstrates handling.

relative_link.py

import os

Create directory structure

os.makedirs(“dir1/dir2”, exist_ok=True) with open(“dir1/file.txt”, “w”) as f: f.write(“Test file”)

Create relative symbolic link

os.chdir(“dir1/dir2”) os.symlink("../file.txt", “rel_link.txt”)

Read the link

link_path = “rel_link.txt” try: target = os.readlink(link_path) print(f"Link points to: {target}")

# Resolve to absolute path
abs_path = os.path.abspath(target)
print(f"Absolute path: {abs_path}")

# Verify the target exists
if os.path.exists(target):
    print("Target exists")
    with open(target) as f:
        print(f.read())
else:
    print("Target doesn't exist")

except OSError as e: print(f"Error: {e}")

Clean up

os.chdir("../..") os.remove(“dir1/dir2/rel_link.txt”) os.remove(“dir1/file.txt”) os.removedirs(“dir1/dir2”)

This creates a relative symbolic link and shows how to read and resolve it. The link target “../file.txt” is relative to the link’s directory.

To use the link target, you often need to convert it to an absolute path using os.path.abspath or os.path.join with the link’s directory.

Symbolic links can also contain absolute paths. os.readlink returns the exact stored path, which may be absolute or relative.

absolute_link.py

import os

Create a file and absolute symlink

target = os.path.abspath(“target_file.txt”) link_name = “abs_link.txt”

with open(target, “w”) as f: f.write(“Absolute path test”)

os.symlink(target, link_name)

Read the absolute link

try: link_target = os.readlink(link_name) print(f"Link points to: {link_target}")

# Compare with resolved path
resolved = os.path.realpath(link_name)
print(f"Resolved path: {resolved}")

# Check if they match
if os.path.abspath(link_target) == resolved:
    print("Link target matches resolved path")
else:
    print("Warning: Link target differs from resolved path")

except OSError as e: print(f"Error: {e}")

Clean up

os.remove(link_name) os.remove(target)

This creates an absolute symbolic link and compares the raw link target with the fully resolved path using os.path.realpath.

For absolute links, the readlink result should match the resolved path unless intermediate links exist or filesystem changes occurred.

Error Handling

os.readlink raises OSError for various conditions. This example demonstrates comprehensive error handling for different failure scenarios.

error_handling.py

import os import errno

test_cases = [ “nonexistent_link”, # Doesn’t exist “regular_file.txt”, # Not a symlink “/proc/self/fd/0”, # Special file (may not be a link) "" # Empty path ]

Create a regular file for testing

with open(“regular_file.txt”, “w”) as f: f.write(“Test”)

for path in test_cases: print(f"\nTesting: {path}") try: target = os.readlink(path) print(f"Link target: {target}") except OSError as e: if e.errno == errno.EINVAL: print(“Not a symbolic link”) elif e.errno == errno.ENOENT: print(“Path does not exist”) elif e.errno == errno.ENOTDIR: print(“Component of path is not a directory”) else: print(f"Unexpected error: {e}")

Clean up

os.remove(“regular_file.txt”)

This tests various error conditions and handles them with specific error messages. Different errno values indicate different failure reasons.

Common errors include EINVAL (not a symlink), ENOENT (doesn’t exist), and EACCES (permission denied). Always handle these cases robustly.

Symbolic links can point to other links. This example shows how to safely resolve nested links without infinite loops.

recursive_resolution.py

import os

def resolve_link(path, max_depth=10): “““Resolve a symbolic link recursively with cycle detection.””” original_path = path seen = set()

while max_depth > 0:
    try:
        if os.path.islink(path):
            if path in seen:
                raise OSError("Detected symbolic link loop")
            seen.add(path)
            path = os.path.join(os.path.dirname(path), os.readlink(path))
        else:
            return path
    except OSError as e:
        print(f"Error resolving {original_path}: {e}")
        return None
    max_depth -= 1

print(f"Maximum resolution depth reached for {original_path}")
return path

Create a chain of symbolic links

os.makedirs(“links”, exist_ok=True) with open(“links/target.txt”, “w”) as f: f.write(“Final target”)

os.symlink(“target.txt”, “links/link2.txt”) os.symlink(“link2.txt”, “links/link1.txt”)

Test resolution

print(“Resolved path:”, resolve_link(“links/link1.txt”))

Test cycle detection

os.symlink(“link1.txt”, “links/link2.txt”) # Create cycle print(“Resolved path:”, resolve_link(“links/link1.txt”))

Clean up

os.remove(“links/link1.txt”) os.remove(“links/link2.txt”) os.remove(“links/target.txt”) os.removedirs(“links”)

This implements safe recursive link resolution with cycle detection. The function follows links until finding a non-link or detecting a loop.

For production use, consider using os.path.realpath instead, which handles this automatically with proper system-level resolution.

Cross-Platform Considerations

Symbolic link behavior varies across platforms. This example demonstrates Windows-specific handling and fallback behavior.

cross_platform.py

import os import sys import platform

def read_link_safe(path): “““Cross-platform symbolic link reading with fallbacks.””” try: if platform.system() == “Windows”: # Windows requires special handling if sys.version_info >= (3, 8): # Python 3.8+ has proper symlink support return os.readlink(path) else: # Older Python on Windows may need admin privileges import ctypes kernel32 = ctypes.windll.kernel32 if not kernel32.CreateSymbolicLinkW: raise OSError(“Symbolic links not supported”) return os.readlink(path) else: # Unix-like systems return os.readlink(path) except (OSError, AttributeError) as e: print(f"Error reading link: {e}") return None

Test the function

test_link = “test_link” try: os.symlink(“target.txt”, test_link) print(“Link target:”, read_link_safe(test_link)) except OSError as e: print(f"Couldn’t create test link: {e}") finally: if os.path.exists(test_link): os.remove(test_link)

This shows platform-specific considerations for symbolic links. Windows has different requirements and capabilities than Unix-like systems.

On Windows, symbolic links often require administrator privileges or developer mode. The implementation also varies across Python versions.

Security Considerations

  • Path validation: Always validate resolved paths before use

  • Symlink attacks: Be aware of TOCTOU race conditions

  • Privilege escalation: Malicious links can point to sensitive files

  • Recursion limits: Implement cycle detection for recursive resolution

  • Platform differences: Behavior varies between Unix and Windows

Best Practices

  • Use os.path.realpath: For most cases, prefer this over manual resolution

  • Handle errors: Always account for possible OSError conditions

  • Validate paths: Check resolved paths against expected locations

  • Limit recursion: Prevent infinite loops in custom resolution code

  • Document assumptions: Note platform requirements and limitations

Source References

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Python tutorials.

ad ad