Complete guide to Python's os.readlink function covering symbolic link resolution, path handling, and practical examples.
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.
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
target = “original.txt” link_name = “symlink.txt”
with open(target, “w”) as f: f.write(“Test content”)
os.symlink(target, link_name)
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}")
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
os.makedirs(“dir1/dir2”, exist_ok=True) with open(“dir1/file.txt”, “w”) as f: f.write(“Test file”)
os.chdir(“dir1/dir2”) os.symlink("../file.txt", “rel_link.txt”)
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}")
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
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)
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}")
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.
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 ]
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}")
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
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”)
print(“Resolved path:”, resolve_link(“links/link1.txt”))
os.symlink(“link1.txt”, “links/link2.txt”) # Create cycle print(“Resolved path:”, resolve_link(“links/link1.txt”))
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.
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_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.
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
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
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.