Complete guide to Python's os.dup function covering file descriptor duplication, redirection, and practical examples.
Last modified April 11, 2025
This comprehensive guide explores Python’s os.dup function, which duplicates file descriptors. We’ll cover descriptor management, redirection techniques, and practical low-level I/O examples.
The os.dup function creates a duplicate of a file descriptor. It returns a new descriptor that refers to the same file/pipe/socket.
Key points: The new descriptor is the lowest-numbered available. Both descriptors share file offset and status flags. Closing one doesn’t affect the other.
This example demonstrates the simplest use of os.dup to create a copy of a file descriptor. Both descriptors can be used interchangeably.
basic_dup.py
import os
with open(“example.txt”, “w”) as f: fd = f.fileno() print(f"Original file descriptor: {fd}")
# Duplicate the descriptor
new_fd = os.dup(fd)
print(f"Duplicated file descriptor: {new_fd}")
# Write using original descriptor
os.write(fd, b"Hello from original fd\n")
# Write using duplicated descriptor
os.write(new_fd, b"Hello from duplicated fd\n")
# Close the duplicated descriptor
os.close(new_fd)
This shows how both descriptors refer to the same file. Writes through either appear in the same file. The context manager closes the original.
Note we manually close the duplicated descriptor since it’s not managed by the context manager.
os.dup is commonly used for I/O redirection. This example redirects stdout to a file while preserving the original stdout.
stdout_redirect.py
import os import sys
with open(“output.log”, “w”) as f: # Save original stdout original_stdout = os.dup(1)
# Redirect stdout to our file
os.dup2(f.fileno(), 1)
# Print to stdout (now goes to file)
print("This goes to the file")
# Restore original stdout
os.dup2(original_stdout, 1)
os.close(original_stdout)
# Print to restored stdout
print("This goes to console")
We first save the original stdout (fd 1), then redirect it to our file. After printing, we restore the original stdout. This is temporary.
os.dup2 is used here as it automatically closes the target descriptor if needed.
This example shows how to create a backup of a descriptor before modifying it, allowing restoration later. Useful for temporary changes.
backup_descriptor.py
import os
def modify_descriptor(fd): # Create backup backup_fd = os.dup(fd)
try:
# Modify the descriptor (simulated here)
print(f"Modifying descriptor {fd}")
# ... actual modification code ...
finally:
# Restore original descriptor
os.dup2(backup_fd, fd)
os.close(backup_fd)
with open(“data.txt”, “r+”) as f: modify_descriptor(f.fileno()) # Original descriptor is restored here print(f.read())
The backup is created before modifications and restored in the finally block. This ensures cleanup even if exceptions occur during modification.
This pattern is useful when you need temporary descriptor changes but want to guarantee cleanup.
This example implements a simple tee command that writes to both stdout and a file using descriptor duplication and piping.
tee_command.py
import os import sys
def tee(output_file): # Create a pipe read_fd, write_fd = os.pipe()
# Fork the process
pid = os.fork()
if pid == 0: # Child process
os.close(write_fd)
# Redirect stdin from pipe
os.dup2(read_fd, 0)
os.close(read_fd)
# Open output file
with open(output_file, "w") as f:
# Read from pipe and write to both stdout and file
while True:
data = os.read(0, 1024)
if not data:
break
os.write(1, data) # stdout
os.write(f.fileno(), data) # file
os._exit(0)
else: # Parent process
os.close(read_fd)
# Redirect stdout to pipe
os.dup2(write_fd, 1)
os.close(write_fd)
tee(“output.log”) print(“This goes to both console and file”) print(“Another line”)
The parent redirects its stdout to a pipe. The child reads from the pipe and writes to both the original stdout and a file. This demonstrates complex descriptor manipulation.
Note the use of os._exit in the child to avoid cleanup issues with Python’s normal exit handling.
This example shows how duplicated descriptors persist across os.exec calls, unlike normal Python file objects.
exec_inheritance.py
import os import sys
with open(“exec_output.txt”, “w”) as f: # Duplicate the descriptor fd = os.dup(f.fileno())
# Execute a new program with the descriptor still open
os.execlp("echo", "echo", "This goes to inherited descriptor",
str(fd))
# This line never reached due to exec
os.close(fd)
The duplicated descriptor remains open in the new process. The executed command (echo in this case) can use the descriptor number passed as argument.
This technique is useful when you need to pass open files to child processes without using file paths.
This example demonstrates a descriptor leak scenario and how to avoid it. Descriptor leaks can cause resource exhaustion in long-running processes.
descriptor_leak.py
import os import time
def leaky_function(): f = open(“leak.txt”, “w”) fd = os.dup(f.fileno()) # Forgot to close fd f.close() # Only closes original descriptor
def safe_function(): f = open(“safe.txt”, “w”) fd = os.dup(f.fileno()) try: # Use the descriptors os.write(fd, b"Safe operation\n") finally: # Always clean up os.close(fd) f.close()
print(“Before leak:”) print(os.listdir("/proc/self/fd")) leaky_function() print("\nAfter leak:") print(os.listdir("/proc/self/fd"))
safe_function() print("\nAfter safe operation:") print(os.listdir("/proc/self/fd"))
The leaky function creates a descriptor but fails to close it. The safe version uses try/finally to ensure cleanup. The /proc listing shows open fds.
Descriptor leaks are particularly problematic in long-running processes like servers, where they can accumulate over time.
Resource management: Always close duplicated descriptors
Descriptor limits: Systems have maximum open descriptors
Privilege escalation: Be careful with privileged descriptors
Atomic operations: Prefer dup2 over separate close/dup
Platform differences: Behavior may vary across systems
Use context managers: Where possible for automatic cleanup
Document ownership: Clearly track who closes descriptors
Prefer dup2: For atomic descriptor replacement
Check returns: Always verify dup/dup2 succeeded
Limit scope: Keep descriptor manipulations localized
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.