ZetCode

Python os.ftruncate 函数

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

本全面指南探讨了 Python 的 os.ftruncate 函数,该函数通过截断或扩展文件来修改文件大小。我们将介绍文件描述符、大小修改和实际的文件操作示例。

基本定义

os.ftruncate 函数更改由文件描述符引用的文件的大小。如果新大小大于当前大小,它可以截断文件或用空字节扩展文件。

关键参数:fd (文件描述符), length (以字节为单位的新大小)。需要对文件具有写权限。可在 Unix 和 Windows 系统上运行。

基本文件截断

此示例演示了使用 os.ftruncate 减小文件大小的最简单方法。我们首先创建一个包含一些内容的示例文件。

basic_truncate.py
import os

# Create a sample file
with open("sample.txt", "w") as f:
    f.write("This is a sample text file with some content.")

# Open file for reading and writing
with open("sample.txt", "r+") as f:
    # Get current size
    original_size = os.fstat(f.fileno()).st_size
    print(f"Original size: {original_size} bytes")

    # Truncate to 10 bytes
    os.ftruncate(f.fileno(), 10)
    truncated_size = os.fstat(f.fileno()).st_size
    print(f"Truncated size: {truncated_size} bytes")

    # Read remaining content
    f.seek(0)
    print(f"Content after truncation: '{f.read()}'")

此代码创建一个文件,然后在读写模式下打开它。它显示原始大小,截断为 10 个字节,并显示新的大小和内容。

文件描述符使用 fileno() 从文件对象获取。截断后,该文件仅包含原始内容的前 10 个字节。

扩展文件

当新大小超过当前大小时,os.ftruncate 还可以通过添加空字节来扩展文件。此示例演示了此行为。

extend_file.py
import os

# Create a small file
with open("small.txt", "w") as f:
    f.write("short")

# Open and extend the file
with open("small.txt", "r+") as f:
    print(f"Original content: '{f.read()}'")
    f.seek(0)
    
    # Extend to 20 bytes
    os.ftruncate(f.fileno(), 20)
    
    # Show extended content
    f.seek(0)
    extended_content = f.read()
    print(f"Extended content: '{extended_content}'")
    print(f"Extended content length: {len(extended_content)}")
    print(f"Extended content as bytes: {extended_content.encode()}")

此代码创建一个小文件,然后将其扩展到 20 个字节。新空间用空字节 (\x00) 填充,这些字节在作为文本打印时可能不可见。

读取扩展文件时,空字节包含在内容中,但可能不会在终端输出中可见,具体取决于您的环境。

截断为零

一个常见的用例是将文件截断为零字节,从而有效地清除其内容,同时保持文件不变。此示例展示了如何执行此操作。

truncate_to_zero.py
import os

# Create a file with content
with open("logfile.log", "w") as f:
    f.write("This is some log content\n" * 10)

# Check original size
original_size = os.path.getsize("logfile.log")
print(f"Original log file size: {original_size} bytes")

# Truncate to zero
with open("logfile.log", "r+") as f:
    os.ftruncate(f.fileno(), 0)

# Verify truncation
new_size = os.path.getsize("logfile.log")
print(f"After truncation: {new_size} bytes")

# File still exists but is empty
with open("logfile.log", "r") as f:
    content = f.read()
    print(f"File content: '{content}' (length: {len(content)})")

此示例创建一个日志文件,然后将其截断为零字节。该文件保留在原位,但不包含任何数据。这对于日志轮换很有用。

请注意,必须以允许写入的模式(在本例中为 r+)打开文件,才能进行截断。文件的元数据(如创建时间)保持不变。

错误处理

os.ftruncate 可能会引发各种异常。此示例展示了针对常见场景(如权限问题)的正确错误处理。

error_handling.py
import os
import errno

def safe_truncate(filename, size):
    try:
        with open(filename, "r+") as f:
            os.ftruncate(f.fileno(), size)
            print(f"Successfully truncated {filename} to {size} bytes")
    except OSError as e:
        if e.errno == errno.EBADF:
            print(f"Error: Bad file descriptor for {filename}")
        elif e.errno == errno.EINVAL:
            print(f"Error: Invalid size {size} for {filename}")
        elif e.errno == errno.EPERM:
            print(f"Error: Permission denied for {filename}")
        else:
            print(f"Unexpected error: {e}")

# Test cases
safe_truncate("existing.txt", 100)  # Assuming this file exists
safe_truncate("/root/protected.txt", 0)  # Likely permission denied
safe_truncate("nonexistent.txt", 50)  # FileNotFoundError
safe_truncate("valid.txt", -10)  # Invalid size

此函数演示了处理截断期间可能发生的各种错误。不同的错误编号指示不同类型的故障。

请注意,尝试截断不存在的文件会引发 FileNotFoundError,我们在 Python 中将其捕获为 OSError。在使用文件操作时,始终处理潜在的错误。

处理二进制文件

os.ftruncate 对二进制文件的处理效果同样出色。此示例显示了在特定位置截断二进制文件。

binary_truncate.py
import os
import struct

# Create a binary file with various data types
with open("data.bin", "wb") as f:
    # Write different data types
    f.write(struct.pack('i', 42))          # Integer
    f.write(struct.pack('f', 3.14))        # Float
    f.write(struct.pack('10s', b"binary")) # String
    f.write(struct.pack('?', True))        # Boolean

# Check original size
original_size = os.path.getsize("data.bin")
print(f"Original binary file size: {original_size} bytes")

# Truncate after the integer and float (8 bytes)
with open("data.bin", "r+b") as f:
    os.ftruncate(f.fileno(), 8)
    
    # Read remaining content
    f.seek(0)
    data = f.read()
    print(f"Remaining bytes: {data}")
    print(f"Unpacked integer: {struct.unpack('i', data[:4])[0]}")
    print(f"Unpacked float: {struct.unpack('f', data[4:8])[0]}")

这将创建一个包含多种数据类型的二进制文件,然后在前两个值之后截断它。剩余的内容被读取和解包以进行验证。

二进制文件截断是精确的,因为我们处理的是精确的字节数。这在处理二进制文件中的固定大小的记录或标头时非常有用。

与 os.truncate 比较

Python 还提供了 os.truncate,它的工作方式类似,但采用路径而不是文件描述符。此示例比较了这两个函数。

compare_truncate.py
import os

filename = "compare.txt"

# Create sample file
with open(filename, "w") as f:
    f.write("This is a file for comparing truncation methods")

# Method 1: Using os.ftruncate with file descriptor
with open(filename, "r+") as f:
    os.ftruncate(f.fileno(), 10)
    f.seek(0)
    fd_content = f.read()
    print(f"os.ftruncate result: '{fd_content}'")

# Reset file
with open(filename, "w") as f:
    f.write("This is a file for comparing truncation methods")

# Method 2: Using os.truncate with path
os.truncate(filename, 10)
with open(filename, "r") as f:
    path_content = f.read()
    print(f"os.truncate result: '{path_content}'")

# Compare
print(f"Results equal: {fd_content == path_content}")

这两种方法都通过不同的接口实现了相同的结果。 os.ftruncate 需要一个打开的文件描述符,而 os.truncate 使用路径字符串。

选择取决于上下文:当您已经打开一个文件时,使用 os.ftruncate;当直接处理文件路径时,使用 os.truncate。

真实世界的日志轮换

此示例展示了 os.ftruncate 在限制日志文件大小的日志轮换系统中的实际应用。

log_rotation.py
import os
import time

LOG_FILE = "app.log"
MAX_SIZE = 1024  # 1KB max log size

def write_log(message):
    # Check current size
    if os.path.exists(LOG_FILE):
        current_size = os.path.getsize(LOG_FILE)
        if current_size >= MAX_SIZE:
            # Rotate log by truncating
            with open(LOG_FILE, "r+") as f:
                os.ftruncate(f.fileno(), 0)
            print("Log rotated (truncated)")
    
    # Append new log entry
    with open(LOG_FILE, "a") as f:
        timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
        f.write(f"[{timestamp}] {message}\n")

# Simulate log writing
for i in range(100):
    write_log(f"Event {i}: This is a log message")
    time.sleep(0.1)  # Small delay

# Show final log size
final_size = os.path.getsize(LOG_FILE)
print(f"Final log size: {final_size} bytes")

此日志轮换系统在每次写入之前检查文件大小。如果日志超过 1KB,它将被截断为零字节。在生产环境中,您需要更复杂的轮换。

实际的实现可能会存档旧日志而不是截断,但这演示了使用 os.ftruncate 进行大小管理的核心概念。

安全注意事项

最佳实践

资料来源

作者

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

列出所有 Python 教程