ZetCode

Python os.dup 函数

上次修改时间:2025 年 4 月 11 日

本综合指南探讨了 Python 的 os.dup 函数,该函数复制文件描述符。我们将介绍描述符管理、重定向技术和实际的底层 I/O 示例。

基本定义

os.dup 函数创建文件描述符的副本。它返回一个指向相同文件/管道/套接字的新描述符。

要点:新描述符是编号最低的可用描述符。两个描述符共享文件偏移量和状态标志。关闭一个不会影响另一个。

基本文件描述符复制

此示例演示了 os.dup 的最简单用法,用于创建文件描述符的副本。两个描述符可以互换使用。

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)

这显示了两个描述符如何引用同一个文件。通过任一描述符写入的内容都会出现在同一文件中。上下文管理器关闭原始描述符。

请注意,我们手动关闭了重复的描述符,因为它不受上下文管理器的管理。

重定向标准输出

os.dup 通常用于 I/O 重定向。此示例将 stdout 重定向到文件,同时保留原始 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")

我们首先保存原始 stdout (fd 1),然后将其重定向到我们的文件。打印后,我们恢复原始 stdout。这是临时的。

此处使用 os.dup2 是因为它会在需要时自动关闭目标描述符。

创建备份描述符

此示例演示了如何在修改描述符之前创建其备份,以便稍后恢复。对于临时更改非常有用。

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

备份是在修改之前创建的,并在 finally 块中恢复。这确保了即使在修改期间发生异常,也能进行清理。

当您需要临时的描述符更改但又想保证清理时,此模式非常有用。

实现 Tee 命令

此示例实现了一个简单的 tee 命令,该命令使用描述符复制和管道将内容写入 stdout 和文件。

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

父进程将其 stdout 重定向到管道。子进程从管道读取数据,并将其写入原始 stdout 和文件。这演示了复杂的描述符操作。

请注意,在子进程中使用 os._exit 可以避免 Python 正常退出处理带来的清理问题。

跨 Exec 的描述符继承

此示例显示了与普通 Python 文件对象不同,复制的描述符如何在 os.exec 调用中保持不变。

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)

复制的描述符在新进程中保持打开状态。执行的命令(本例中为 echo)可以使用作为参数传递的描述符编号。

当您需要将打开的文件传递给子进程而不使用文件路径时,此技术非常有用。

创建文件描述符泄漏

此示例演示了一个描述符泄漏场景以及如何避免它。描述符泄漏可能导致长时间运行的进程中的资源耗尽。

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

leaky 函数创建一个描述符,但未能关闭它。安全的版本使用 try/finally 来确保清理。/proc 列表显示打开的 fds。

描述符泄漏在服务器等长时间运行的进程中尤其成问题,因为它们会随着时间的推移而累积。

安全注意事项

最佳实践

资料来源

作者

我的名字是 Jan Bodnar,我是一位充满热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出所有 Python 教程