一、 核心流程
打开数据源 → 逐行读取 → 解析处理 → 写入结果 → 关闭文件
二、 关键点
1. 打开文件
cpp
open(argv[1], O_RDONLY); // 只读打开数据源
open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0644); // 读写/创建/清空
- 使用
O_TRUNC可以让每次运行都覆盖旧文件 - 权限
0644是通用的文本文件权限
2. 逐行读取:自己造轮子
cpp
read_line(fd, buf) // 一个字节一个字节读,直到碰到 \n 或 \r
要点:
- 系统自带的
read()是"流式"的,它不知道什么是"行" - 需要自己判断行结束符:
\n(换行)或\r(回车) - 读到这两个字符就停止,把前面的内容当成一行
- 逻辑如下:

3. 解析 CSV 格式
cpp
sscanf(data_buf, "%[^,],%d,%d,%d,", name, &scores[0], &scores[1], &scores[2]);
技巧:
%[^,]→ 读取直到碰到逗号为止(用来读取名字)%d→ 读取整数(用来读取成绩)- 这样就把
"张三,90,91,92,,"拆成了变量
4. 业务处理:
cpp
sum = scores[0] + scores[1] + scores[2]; // 算总分 // 根据分数评级...
使用代码,备注如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
/* ==========================================
* 函数功能:从文件中读取一行数据
* 参数:
* fd: 文件描述符
* buf: 存储读取数据的缓冲区
* 返回值:
* >= 0: 成功读取一行,返回实际读取的字符数(不含换行符)
* -1: 到达文件末尾或读取出错
* ==========================================
*/
static int read_line(int fd, unsigned char *buf)
{
/* 循环读入一个字符 */
/* 如何判断已经读完一行? 读到0x0d('\r'), 0x0a('\n') */
unsigned char c; // 存放每次读取的一个字符
int len; // read()的返回值
int i = 0; // 缓冲区索引
int err = 0; // 错误标志:0=成功,-1=失败
while (1) // 无限循环,直到读到换行符或文件结束
{
// 每次只读取1个字符
len = read(fd, &c, 1);
if (len <= 0) // 读取失败(len < 0) 或 到达文件末尾(len = 0)
{
err = -1; // 标记出错
break; // 跳出循环
}
else // 成功读取到一个字符
{
// 判断是否是换行符或回车符
if (c != '\n' && c != '\r')
{
// 不是换行符/回车符:存入缓冲区
buf[i] = c;
i++;
}
else
{
// 碰到回车或换行符:表示这一行结束了
err = 0; // 标记成功
break; // 跳出循环
}
}
}
// 在读取的数据末尾加上字符串结束符,变成C字符串
buf[i] = '\0';
// 判断返回值
if (err && (i == 0))
{
// 读到文件尾部了并且一个数据都没有读进来
return -1;
}
else
{
// 返回这一行的字符个数(不含换行符)
return i;
}
}
/* ==========================================
* 函数功能:处理CSV格式的成绩数据
* 输入格式:"姓名,语文,数学,英语"
* 输出格式:"姓名,语文,数学,英语,总分,评价"
* 参数:
* data_buf: 原始数据(输入)
* result_buf: 处理结果(输出)
* ==========================================
*/
void process_data(unsigned char *data_buf, unsigned char *result_buf)
{
/*
* 示例1(表头):
* data_buf = ",语文,数学,英语,总分,评价"
* result_buf = ",语文,数学,英语,总分,评价" (直接复制)
*
* 示例2(学生成绩):
* data_buf = "张三,90,91,92,,"
* result_buf = "张三,90,91,92,273,A+" (计算总分和评级)
*/
char name[100]; // 学生姓名
int scores[3]; // 三科成绩:语文、数学、英语
int sum; // 总分
char *levels[] = {"A+", "A", "B"}; // 评级数组
int level; // 评级索引
// 判断是不是UTF-8编码的文件头(前3个字符是0xef 0xbb 0xbf)
if (data_buf[0] == 0xef)
{
// 是表头行:直接复制到结果缓冲区
strcpy(result_buf, data_buf);
}
else
{
// 是学生成绩行:解析CSV格式
// 格式:"姓名,语文,数学,英语,"
// 使用sscanf按逗号分隔提取数据
sscanf(data_buf, "%[^,],%d,%d,%d,",
name, // %[^,] = 读取到逗号为止(姓名)
&scores[0], // 语文成绩
&scores[1], // 数学成绩
&scores[2]); // 英语成绩
// 计算总分
sum = scores[0] + scores[1] + scores[2];
// 根据总分评级
if (sum >= 270)
level = 0; // A+
else if (sum >= 240)
level = 1; // A
else
level = 2; // B
// 格式化输出结果:"姓名,语文,数学,英语,总分,评级"
sprintf(result_buf, "%s,%d,%d,%d,%d,%s",
name,
scores[0], scores[1], scores[2],
sum,
levels[level]);
}
}
/* ==========================================
* 主函数:CSV成绩处理程序
* 使用方法:./process_excel data.csv result.csv
* 参数:
* argc = 3(程序名 + 输入文件 + 输出文件)
* argv[0] = "./process_excel"
* argv[1] = "data.csv"(输入的成绩文件)
* argv[2] = "result.csv"(输出的结果文件)
* ==========================================
*/
int main(int argc, char **argv)
{
int fd_data, fd_result; // 两个文件描述符:输入文件、输出文件
int i; // 循环变量(定义了但没用到)
int len; // 读取的行数
unsigned char data_buf[1000]; // 输入数据缓冲区
unsigned char result_buf[1000]; // 结果数据缓冲区
// 检查参数个数:必须提供输入和输出两个文件名
if (argc != 3)
{
printf("Usage: %s <data csv file> <result csv file>\n", argv[0]);
return -1;
}
// ==========================================
// 第一步:打开输入文件(只读)
// ==========================================
fd_data = open(argv[1], O_RDONLY);
if (fd_data < 0)
{
printf("can not open file %s\n", argv[1]);
perror("open");
return -1;
}
else
{
printf("data file fd = %d\n", fd_data);
}
// ==========================================
// 第二步:打开/创建输出文件(读写模式)
// ==========================================
fd_result = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0644);
/*
* O_RDWR: 读写模式
* O_CREAT: 文件不存在就创建
* O_TRUNC: 文件已存在就清空
* 0644: 权限 rw-r--r--
*/
if (fd_result < 0)
{
printf("can not create file %s\n", argv[2]);
perror("create");
return -1;
}
else
{
printf("resultfile fd = %d\n", fd_result);
}
// ==========================================
// 第三步:循环处理每一行
// ==========================================
while (1)
{
// 从数据文件里读取1行
len = read_line(fd_data, data_buf);
if (len == -1)
{
// 到达文件末尾,结束循环
break;
}
// if (len != 0)
// printf("line: %s\n\r", data_buf);
if (len != 0) // 跳过空行
{
// 处理数据:计算总分和评级
process_data(data_buf, result_buf);
// 写入结果文件
// write_data(fd_result, result_buf); // 注释掉的原代码
write(fd_result, result_buf, strlen(result_buf));
// 写入换行符(Windows风格 \r\n)
write(fd_result, "\r\n", 2);
}
}
// ==========================================
// 第四步:关闭文件
// ==========================================
close(fd_data);
close(fd_result);
return 0;
}
这是一个CSV格式的成绩处理工具!
输入文件格式(data.csv):
,语文,数学,英语,总分,评价
张三, 90, 91, 92, ,
李四, 85, 88, 90, ,
王五, 70, 75, 80, ,
输出文件格式(result.csv):
,语文,数学,英语,总分,评价
张三,90, 91, 92, 273, A+
李四,85, 88, 90, 263, A
王五,70, 75, 80, 225, B
🏗️ 程序结构
三个主要函数:
| 函数 | 作用 |
|---|---|
read_line() |
逐行读取文件 |
process_data() |
处理一行数据(计算总分、评级) |
main() |
主函数,协调整个流程 |
🔍 核心功能详解
1. read_line() - 逐行读取
特点:
-
每次只读1个字符
-
遇到
\n或\r就认为一行结束 -
自动去掉换行符
-
返回这一行的字符数
为什么不直接读一行?
-
CSV文件可能来自Windows或Linux
-
换行符可能是
\n或\r\n -
逐字符读更灵活、兼容性更好
2. process_data() - 数据处理
两个判断分支:
| 情况 | 判断条件 | 处理方式 |
|---|---|---|
| 表头行 | data_buf[0] == 0xef (UTF-8 BOM) |
直接复制 |
| 数据行 | 其他情况 | 解析、计算总分、评级 |
评级规则:
-
总分 ≥ 270 → A+
-
总分 ≥ 240 → A
-
其他 → B
CSV解析关键:
sscanf(data_buf, "%[^,],%d,%d,%d,",
name, &scores[0], &scores[1], &scores[2]);
| 格式符 | 说明 |
|---|---|
%[^,] |
读取到逗号为止(姓名) |
%d |
读取整数(成绩) |
3. main() - 主流程
1. 打开输入文件 data.csv
↓
2. 打开/创建输出文件 result.csv
↓
3. 循环:
├─ 读取一行 read_line()
├─ 到达EOF?→ 是 → 结束
└─ 否 → process_data() 处理 → write() 写入结果
↓
4. 关闭两个文件
💡 这个程序的学习价值
| 知识点 | 应用位置 |
|---|---|
| 文件IO | open(), read(), write(), close() |
| 逐行读取 | read_line() 函数 |
| 字符串处理 | sscanf(), sprintf(), strcpy() |
| CSV格式解析 | %[^,] 格式符的妙用 |
| 缓冲区操作 | 手动加 \0 结束符 |


后续会写一个自己的工程放在github