使用linux的io文件操作综合实验_处理表格

一、 核心流程

复制代码
打开数据源 → 逐行读取 → 解析处理 → 写入结果 → 关闭文件

二、 关键点

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

相关推荐
扁舟·TF1 小时前
VirtuaBox: 修改 Host-Only 网络的 IP 地址
服务器·网络·tcp/ip
计算机安禾1 小时前
【C语言程序设计】第36篇:二进制文件的读写
c语言·开发语言·c++·算法·github·visual studio code·visual studio
ZPC82102 小时前
OLOv11 + 深度相机的方案实现高精度3D定位
人工智能·数码相机·算法·机器人
_日拱一卒2 小时前
LeetCode:字母异位词分组
算法·leetcode·职场和发展
Dfreedom.2 小时前
机器学习经典算法全景解析与演进脉络(监督学习篇)
人工智能·学习·算法·机器学习·监督学习
wd5205212 小时前
常用环境部署(二十九)——Centos升级OpenSSH 10.2p1
linux·运维·centos·ssh
顶点多余2 小时前
Ext文件系统详解
linux·运维·服务器
2301_807367192 小时前
C++代码风格检查工具
开发语言·c++·算法
Morwit2 小时前
*【力扣hot100】 215. 数组中的第K个最大元素
数据结构·c++·算法·leetcode·职场和发展