共计 2448 个字符,预计需要花费 7 分钟才能阅读完成。
在开发文件复制工具时,新手常遇到三个典型问题:参数校验缺失导致程序崩溃、文件权限管理混乱引发安全风险、直接逐字节读写导致的性能低下。本文将用 C 语言一步步解决这些问题,并给出可直接复用的代码模板。

命令行参数解析
先处理最基本的参数校验。通过 main 函数的 argc 和 argv 参数,我们可以获取用户输入的命令行参数:
- 参数数量检查:复制操作需要源文件路径和目标文件路径两个参数
- 参数不足时打印使用说明(usage)
- 参数顺序约定:第一个参数是源文件,第二个是目标文件
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]) {if (argc != 3) {fprintf(stderr, "Usage: %s <source> <destination>\n", argv[0]);
return 1;
}
const char *src_path = argv[1];
const char *dst_path = argv[2];
// 后续操作...
}
文件操作规范
打开文件时需要特别注意权限控制:
- 源文件:以只读方式打开(O_RDONLY)
- 目标文件:组合使用三个标志位
- O_WRONLY:只写模式
- O_CREAT:不存在时创建
- O_TRUNC:存在时清空
- 权限位 0644 表示:
- 用户可读写(6)
- 组用户可读(4)
- 其他用户可读(4)
注意实际权限会受到 umask 影响,最终权限 = 模式 & ~umask
int src_fd = open(src_path, O_RDONLY);
if (src_fd == -1) {perror("Failed to open source file");
return 1;
}
int dst_fd = open(dst_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (dst_fd == -1) {perror("Failed to open destination file");
close(src_fd);
return 1;
}
缓冲区读写优化
直接逐字节读写(如每次读写 1 字节)会导致频繁的系统调用,性能极差。推荐使用 8KB 缓冲区,这是经过验证的最佳实践:
- 缓冲区大小选择依据:
- 太小(<1KB):系统调用开销大
- 太大(>32KB):内存占用高,收益递减
- 8KB 是多数文件系统的块大小
#define BUF_SIZE 8192 // 8KB 缓冲区
char buf[BUF_SIZE];
ssize_t bytes_read;
while ((bytes_read = read(src_fd, buf, BUF_SIZE)) > 0) {ssize_t bytes_written = write(dst_fd, buf, bytes_read);
if (bytes_written != bytes_read) {perror("Write error");
close(src_fd);
close(dst_fd);
return 1;
}
}
if (bytes_read == -1) {perror("Read error");
close(src_fd);
close(dst_fd);
return 1;
}
close(src_fd);
close(dst_fd);
return 0;
避坑指南
开发时特别注意这几个常见问题:
- 文件覆盖风险 :O_TRUNC 会无条件清空已存在的目标文件,生产环境应考虑添加确认提示
- 权限问题 :
- 检查源文件是否有读权限
- 检查目标目录是否有写权限
- umask 可能导致实际权限与预期不符
- 资源泄漏 :
- 必须 close 所有打开的文件描述符
- 错误路径也需要关闭已打开的文件
- 错误处理 :
- 每次系统调用后都应检查返回值
- 使用 perror 输出有意义的错误信息
完整代码示例
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define BUF_SIZE 8192
int main(int argc, char *argv[]) {
// 参数校验
if (argc != 3) {fprintf(stderr, "Usage: %s <source> <destination>\n", argv[0]);
return 1;
}
// 打开源文件
int src_fd = open(argv[1], O_RDONLY);
if (src_fd == -1) {perror("Failed to open source file");
return 1;
}
// 创建 / 截断目标文件,权限 0644
int dst_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (dst_fd == -1) {perror("Failed to create destination file");
close(src_fd);
return 1;
}
// 缓冲区读写
char buf[BUF_SIZE];
ssize_t bytes_read;
while ((bytes_read = read(src_fd, buf, BUF_SIZE)) > 0) {if (write(dst_fd, buf, bytes_read) != bytes_read) {perror("Write error");
close(src_fd);
close(dst_fd);
return 1;
}
}
// 错误检查
if (bytes_read == -1) {perror("Read error");
close(src_fd);
close(dst_fd);
return 1;
}
// 关闭文件
close(src_fd);
close(dst_fd);
return 0;
}
进阶思考
这个基础版本还可以进一步扩展:
- 如何支持目录复制?(需要递归处理目录结构)
- 如何添加进度显示?(获取文件大小后用百分比显示)
- 如何保留文件属性?(如时间戳、权限等)
- 如何实现断点续传?(记录已复制的偏移量)
建议从最简单的进度显示功能开始尝试扩展,这需要用到 fstat 获取文件大小,然后在循环中计算已复制的百分比。对于更复杂的需求,可以考虑使用更高级的文件操作 API 或者现成的库。
正文完
发表至: 未分类
近三天内
