ftruncate – 通过文件描述符截断文件
函数介绍
ftruncate
系统调用用于将已打开的文件截断到指定长度。与truncate
不同,ftruncate
通过文件描述符而不是文件路径来操作文件,这在某些情况下更加高效和安全。
函数原型
#include <unistd.h>
#include <sys/types.h>
int ftruncate(int fd, off_t length);
功能
通过文件描述符将文件截断或扩展到指定长度。
参数
int fd
: 文件描述符off_t length
: 目标文件长度(字节)
返回值
- 成功时返回0
- 失败时返回-1,并设置errno:
EBADF
: 文件描述符无效或不是为写入而打开EFBIG
: 长度参数过大EINVAL
: 文件描述符不支持截断EIO
: I/O错误EPERM
: 操作不被允许EROFS
: 文件在只读文件系统上
相似函数
truncate()
: 通过文件路径截断文件open()
配合O_TRUNC
标志
示例代码
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
int main() {
int fd;
struct stat stat_buf;
printf("=== Ftruncate函数示例 ===\n");
// 示例1: 基本的文件截断操作
printf("\n示例1: 基本的文件截断操作\n");
// 创建测试文件并写入数据
fd = open("test_ftruncate.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
exit(EXIT_FAILURE);
}
// 写入测试数据
const char *original_data =
"Line 1: This is the first line of data.\n"
"Line 2: This is the second line of data.\n"
"Line 3: This is the third line of data.\n"
"Line 4: This is the fourth line of data.\n"
"Line 5: This is the fifth line of data.\n";
if (write(fd, original_data, strlen(original_data)) == -1) {
perror("写入原始数据失败");
close(fd);
exit(EXIT_FAILURE);
}
printf("写入原始数据,长度: %ld 字节\n", (long)strlen(original_data));
// 获取原始文件状态
if (fstat(fd, &stat_buf) == -1) {
perror("获取文件状态失败");
} else {
printf("原始文件大小: %ld 字节\n", stat_buf.st_size);
}
// 使用ftruncate截断文件到80字节
printf("\n使用ftruncate将文件截断到80字节...\n");
if (ftruncate(fd, 80) == -1) {
perror("截断文件失败");
} else {
printf("文件截断成功\n");
}
// 检查截断后的文件状态
if (fstat(fd, &stat_buf) == -1) {
perror("获取截断后文件状态失败");
} else {
printf("截断后文件大小: %ld 字节\n", stat_buf.st_size);
}
// 读取截断后的数据验证
lseek(fd, 0, SEEK_SET); // 重置文件位置指针
char buffer[200];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("截断后文件内容:\n%s", buffer);
}
// 示例2: 扩展文件
printf("\n示例2: 扩展文件\n");
// 使用ftruncate将文件扩展到150字节
printf("使用ftruncate将文件扩展到150字节...\n");
if (ftruncate(fd, 150) == -1) {
perror("扩展文件失败");
} else {
printf("文件扩展成功\n");
}
// 检查扩展后的文件状态
if (fstat(fd, &stat_buf) == -1) {
perror("获取扩展后文件状态失败");
} else {
printf("扩展后文件大小: %ld 字节\n", stat_buf.st_size);
}
// 读取扩展后的数据
lseek(fd, 0, SEEK_SET); // 重置文件位置指针
bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
printf("扩展后文件前%d字节内容:\n", (int)bytes_read);
// 显示前100个字符
for (int i = 0; i < bytes_read && i < 100; i++) {
if (buffer[i] >= 32 && buffer[i] <= 126) {
putchar(buffer[i]);
} else if (buffer[i] == '\n') {
putchar('\n');
} else {
printf("[%02x]", (unsigned char)buffer[i]);
}
}
printf("\n");
}
// 示例3: 截断到0字节(清空文件)
printf("\n示例3: 截断到0字节(清空文件)\n");
printf("将文件截断到0字节...\n");
if (ftruncate(fd, 0) == -1) {
perror("清空文件失败");
} else {
printf("文件清空成功\n");
}
if (fstat(fd, &stat_buf) == -1) {
perror("获取清空后文件状态失败");
} else {
printf("清空后文件大小: %ld 字节\n", stat_buf.st_size);
}
// 示例4: 与truncate的对比
printf("\n示例4: ftruncate与truncate对比\n");
// 重新写入数据
const char *compare_data = "Data for comparison test: ftruncate vs truncate";
if (write(fd, compare_data, strlen(compare_data)) == -1) {
perror("写入对比数据失败");
} else {
printf("写入对比数据: %s\n", compare_data);
}
// 使用ftruncate截断
clock_t ftruncate_start = clock();
if (ftruncate(fd, 20) == -1) {
perror("ftruncate失败");
} else {
clock_t ftruncate_time = clock() - ftruncate_start;
printf("ftruncate成功,耗时: %f 秒\n",
((double)ftruncate_time) / CLOCKS_PER_SEC);
}
// 重新创建文件测试truncate
close(fd);
unlink("test_ftruncate.txt");
fd = open("test_ftruncate.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd != -1) {
if (write(fd, compare_data, strlen(compare_data)) == -1) {
perror("重新写入数据失败");
}
close(fd);
// 使用truncate截断
clock_t truncate_start = clock();
if (truncate("test_ftruncate.txt", 20) == -1) {
perror("truncate失败");
} else {
clock_t truncate_time = clock() - truncate_start;
printf("truncate成功,耗时: %f 秒\n",
((double)truncate_time) / CLOCKS_PER_SEC);
}
}
// 重新打开文件继续演示
fd = open("test_ftruncate.txt", O_RDWR);
if (fd == -1) {
perror("重新打开文件失败");
exit(EXIT_FAILURE);
}
// 示例5: 不同长度的截断演示
printf("\n示例5: 不同长度的截断演示\n");
// 重新写入测试数据
const char *test_data = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
lseek(fd, 0, SEEK_SET);
if (ftruncate(fd, 0) == -1) { // 先清空
perror("清空文件失败");
}
if (write(fd, test_data, strlen(test_data)) == -1) {
perror("写入测试数据失败");
}
printf("重新写入测试数据: %s\n", test_data);
// 演示不同长度的截断
off_t lengths[] = {10, 25, 40, 70};
for (int i = 0; i < 4; i++) {
printf("截断到%ld字节: ", lengths[i]);
if (ftruncate(fd, lengths[i]) == -1) {
printf("失败 - %s\n", strerror(errno));
} else {
if (fstat(fd, &stat_buf) == -1) {
printf("获取状态失败\n");
} else {
printf("成功,新大小: %ld字节\n", stat_buf.st_size);
// 读取并显示内容
lseek(fd, 0, SEEK_SET);
char read_buffer[100];
ssize_t bytes_read = read(fd, read_buffer, stat_buf.st_size);
if (bytes_read > 0) {
read_buffer[bytes_read] = '\0';
printf(" 内容: %s\n", read_buffer);
}
}
}
}
// 示例6: 错误处理演示
printf("\n示例6: 错误处理演示\n");
// 尝试对无效文件描述符操作
if (ftruncate(999, 100) == -1) {
printf("对无效文件描述符操作: %s\n", strerror(errno));
}
// 尝试对只读文件描述符操作
int readonly_fd = open("test_ftruncate.txt", O_RDONLY);
if (readonly_fd != -1) {
if (ftruncate(readonly_fd, 50) == -1) {
printf("对只读文件描述符操作: %s\n", strerror(errno));
}
close(readonly_fd);
}
// 尝试使用负数长度
if (ftruncate(fd, -10) == -1) {
printf("使用负数长度: %s\n", strerror(errno));
}
// 尝试对管道或套接字操作(需要特殊文件描述符)
int pipefd[2];
if (pipe(pipefd) == 0) {
if (ftruncate(pipefd[0], 100) == -1) {
printf("对管道操作: %s\n", strerror(errno));
}
close(pipefd[0]);
close(pipefd[1]);
}
// 示例7: 实际应用场景
printf("\n示例7: 实际应用场景\n");
// 场景1: 编辑器临时文件管理
printf("场景1: 编辑器临时文件管理\n");
int editor_fd = open("editor_temp.txt", O_CREAT | O_RDWR, 0644);
if (editor_fd != -1) {
// 模拟编辑器写入大量临时数据
char temp_data[100];
for (int i = 0; i < 50; i++) {
sprintf(temp_data, "Temporary line %d for editor\n", i);
write(editor_fd, temp_data, strlen(temp_data));
}
if (fstat(editor_fd, &stat_buf) == 0) {
printf("临时文件大小: %ld 字节\n", stat_buf.st_size);
// 当用户撤销操作时,可能需要截断文件
off_t undo_position = stat_buf.st_size - 200; // 模拟撤销到某个位置
if (undo_position > 0) {
printf("撤销操作,截断到位置: %ld\n", undo_position);
if (ftruncate(editor_fd, undo_position) == -1) {
perror("撤销操作失败");
} else {
printf("撤销操作成功\n");
}
}
}
close(editor_fd);
unlink("editor_temp.txt");
}
// 场景2: 数据库文件维护
printf("场景2: 数据库文件维护\n");
int db_fd = open("database.dat", O_CREAT | O_RDWR, 0644);
if (db_fd != -1) {
// 模拟数据库文件增长
char db_record[50];
for (int i = 0; i < 100; i++) {
sprintf(db_record, "Database record %d with some data\n", i);
write(db_fd, db_record, strlen(db_record));
}
if (fstat(db_fd, &stat_buf) == 0) {
printf("数据库文件原始大小: %ld 字节\n", stat_buf.st_size);
// 模拟数据库压缩操作
off_t compressed_size = stat_buf.st_size * 0.8; // 压缩到80%
printf("压缩数据库文件到: %ld 字节\n", compressed_size);
if (ftruncate(db_fd, compressed_size) == -1) {
perror("数据库压缩失败");
} else {
printf("数据库压缩成功\n");
}
}
close(db_fd);
unlink("database.dat");
}
// 场景3: 日志文件循环
printf("场景3: 日志文件循环\n");
int log_fd = open("circular_log.txt", O_CREAT | O_RDWR, 0644);
if (log_fd != -1) {
// 写入日志数据直到达到限制
const char *log_entry = "LOG: System operation completed successfully\n";
for (int i = 0; i < 20; i++) {
char entry[100];
sprintf(entry, "Entry %d: %s", i, log_entry);
write(log_fd, entry, strlen(entry));
}
if (fstat(log_fd, &stat_buf) == 0) {
printf("日志文件当前大小: %ld 字节\n", stat_buf.st_size);
// 如果超过限制(比如1KB),保留最后512字节
if (stat_buf.st_size > 1024) {
printf("日志文件过大,保留最后512字节\n");
// 读取最后512字节
char last_data[512];
off_t start_pos = stat_buf.st_size - 512;
if (start_pos > 0) {
lseek(log_fd, start_pos, SEEK_SET);
ssize_t bytes_read = read(log_fd, last_data, 512);
if (bytes_read > 0) {
// 截断文件到0,然后写入保留的数据
ftruncate(log_fd, 0);
lseek(log_fd, 0, SEEK_SET);
write(log_fd, last_data, bytes_read);
printf("日志循环完成,新大小: %ld 字节\n", (long)bytes_read);
}
}
}
}
close(log_fd);
unlink("circular_log.txt");
}
// 清理资源
printf("\n清理资源...\n");
if (close(fd) == -1) {
perror("关闭文件失败");
} else {
printf("成功关闭文件描述符 %d\n", fd);
}
if (unlink("test_ftruncate.txt") == -1) {
perror("删除测试文件失败");
} else {
printf("成功删除测试文件\n");
}
return 0;
}