ZetCode

Python os.dup Function

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.

Basic Definitions

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.

Basic File Descriptor Duplication

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

# Open a file and get its descriptor
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.

Redirecting Standard Output

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

# Open a file for writing
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.

Creating a Backup Descriptor

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)

# Example usage
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.

Implementing a Tee Command

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)

# Usage
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.

Descriptor Inheritance Across Exec

This example shows how duplicated descriptors persist across os.exec calls, unlike normal Python file objects.

exec_inheritance.py
import os
import sys

# Create a temporary file
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.

Creating a File Descriptor Leak

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()

# Demonstrate the leak
print("Before leak:")
print(os.listdir("/proc/self/fd"))
leaky_function()
print("\nAfter leak:")
print(os.listdir("/proc/self/fd"))

# Show safe version
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.

Security Considerations

Best Practices

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.