侧边栏壁纸
    • 累计撰写 302 篇文章
    • 累计收到 525 条评论
    Linux之不使用命令删除文件中的第N行
    我的学记|刘航宇的博客

    Linux之不使用命令删除文件中的第N行

    刘航宇
    2024-07-05 / 0 评论 / 44 阅读 / 正在检测是否收录...

    1.问题描述

    设计一个程序,通过命令行参数接收一个文件名 filename.txt (纯文本文件)和一个整型数字 n,实现从 filename.txt 中删除第 n 行数据。

    2. 解题思路:

      (1) 借助临时文件: 将文件逐行读取,跳过要删除的行,并将其写入临时文件,然后删除源文件,重命名临时文件为源文件,完成删除指定行数据。
      (2) 不借助临时文件: 将文件以读写方式打开,读取到要删除行后,通过移动文件指针将文件后面所有行前移一行,但是最后一行会重复,可以通过截断文件操作完成在源文件上删除指定行数据。
      (3) 通过sed 或 awk 删除文件指定行

    3. 代码实现:

    (1) 通过 fopen 打开文件借助临时文件删除指定行数据

    #filename:ques_15a.c
    #include <stdio.h> // 包含标准输入输出库
    #include <stdlib.h> // 包含标准库函数,比如exit
    
    int main(int argc, char *argv[]) { 
        // 检查命令行参数的数量是否正确
        if (argc != 3) {
            printf("Usage: ./a.out filename num\n");
            exit(EXIT_FAILURE); // 如果不正确,打印用法信息并退出
        }
    
        char buf[4096]; // 定义一个足够大的字符数组用于读取文件行
        int linenum = atoi(argv[2]); // 将命令行参数中的行号转换为整数
    
        FILE *fp = fopen(argv[1], "r"); // 尝试以只读模式打开源文件
        FILE *fpt = fopen("temp.txt", "w"); // 创建一个临时文件用于写入
    
        // 如果源文件无法打开,打印错误信息并退出
        if (!fp) {
            printf("File %s not exist!\n", argv[1]);
            exit(EXIT_FAILURE);
        }
    
        // 检查是否有权限修改源文件
        char str[100];
        sprintf(str, "%s%s", "test -w ", argv[1]);
        if (system(str)) { // 如果没有写权限
            printf("Can't modify file %s, permission denied!\n", argv[1]);
            exit(EXIT_FAILURE); // 打印错误信息并退出
        }
    
        int total_line = 0; // 初始化文件总行数计数器
        while (fgets(buf, sizeof(buf), fp)) { // 读取文件的每一行
            total_line++;
        }
        fseek(fp, 0, SEEK_SET); // 将文件指针重置到文件的开头
    
        // 如果要删除的行数大于文件的总行数,打印错误信息并退出
        if (linenum > total_line) {
            printf("%d is greater than total_line!\n", linenum);
            exit(EXIT_FAILURE);
        }
    
        int i = 0; // 初始化当前行计数器
        while (fgets(buf, sizeof(buf), fp)) { // 再次读取文件的每一行
            i++; // 当前行数加1
            if (i != linenum) { // 如果当前行不是要删除的行
                fputs(buf, fpt); // 将当前行写入临时文件
            }
        }
    
        remove(argv[1]); // 删除原始文件
        rename("temp.txt", argv[1]); // 将临时文件重命名为原始文件名
    
        // 关闭文件指针
        fclose(fp); 
        fclose(fpt);
    
        return 0; // 程序正常退出
    }

    (2) 通过 Linux 系统调用 open 打开文件,需要自定义读取一行的函数,不借助临时文件删除指定行数据

    # filename:ques_15b.c
    #include <stdio.h>    // 包含标准输入输出库
    #include <stdlib.h>   // 包含标准库函数,比如atoi和exit
    #include <string.h>   // 包含字符串操作函数,比如strlen
    #include <fcntl.h>    // 包含文件控制的定义
    #include <sys/stat.h> // 包含文件状态的定义
    #include <sys/types.h>// 包含各种数据类型
    #include <unistd.h>   // 包含UNIX标准函数定义
    
    // 函数声明:读取一行文件内容到缓冲区
    int readline(int fd, char *buf) {
        int t = 0; // 用于记录读取的字符数
        // 循环读取直到遇到换行符
        for (; ;) {
            read(fd, &buf[t], 1); // 从文件描述符fd读取一个字符到buf
            t++; // 增加读取的字符数
            if (buf[t-1] == '\n') { // 如果读取到换行符
                break; // 退出循环
            }
        }
        return t; // 返回读取的字符数
    }
    
    // 函数声明:获取文件的大小和行数
    int get_file_info(int fd, int *size) {
        int num = 0; // 记录行数
        char ch;     // 临时变量用于存储读取的字符
    
        // 循环读取直到文件结束
        while (read(fd, &ch, 1) > 0) {
            (*size)++; // 文件大小加1
            if (ch == '\n') { // 如果读取到换行符
                num++; // 行数加1
            }
        }
    
        return num; // 返回行数
    }
    
    int main(int argc, char *argv[]) {
        // 检查命令行参数数量
        if (argc != 3) {
            printf("Usage: ./a.out filename num\n");
            exit(EXIT_FAILURE); // 参数不正确时退出
        }
    
        int fd; // 文件描述符
        char buf[4096]; // 缓冲区
        int linenum = atoi(argv[2]); // 将命令行参数转换为整数
    
        // 尝试以读写模式打开文件
        fd = open(argv[1], O_RDWR);
        if (fd < 0) {
            printf("Can't open file %s, file not exist or permission denied!\n", argv[1]);
            exit(EXIT_FAILURE); // 打开失败时退出
        }
    
        int size = 0; // 文件大小
        // 获取文件的行数和大小
        int total_line = get_file_info(fd, &size);
    
        // 如果要删除的行数大于文件总行数,退出
        if (linenum > total_line) {
            printf("%d is greater than total_line!\n", linenum);
            exit(EXIT_FAILURE);
        }
    
        int s = 0; // 要删除行的大小
        int t = 0; // 当前行的大小
        int i = 0; // 当前行数
        lseek(fd, 0, SEEK_SET); // 将文件指针移到文件头
    
        // 循环读取文件,直到文件结束
        while (read(fd, &buf[0], 1) > 0) {
            lseek(fd, -1, SEEK_CUR); // 回退一个字符
            memset(buf, 0, sizeof(buf)); // 清空缓冲区
            readline(fd, buf); // 读取一行到缓冲区
            i++; // 行数加1
            t = strlen(buf); // 当前行的大小
    
            // 如果当前行是目标行,记录其大小
            if (i == linenum) {
                s = t;
            }
    
            // 如果当前行在目标行之后,将该行前移
            if (i > linenum) {
                lseek(fd, -(s+t), SEEK_CUR); // 移动文件指针
                write(fd, buf, strlen(buf)); // 写入当前行
                lseek(fd, s, SEEK_CUR); // 移动文件指针
            }
        }
    
        ftruncate(fd, size-s); // 截断文件,删除指定行
        close(fd); // 关闭文件描述符
    
        return 0; // 正常退出
    }

    (3) 通过 fopen 打开文件,不借助临时文件删除指定行数据

    # filename:ques_15b.c
    #include <stdio.h>    // 包含标准输入输出库
    #include <stdlib.h>   // 包含标准库函数,如atoi和exit
    #include <string.h>   // 包含字符串处理函数,如strlen
    #include <math.h>     // 包含数学函数,虽然在这个程序中没有使用
    #include <unistd.h>   // 包含UNIX标准函数,如truncate
    
    int main(int argc, char *argv[]) { 
        // 检查命令行参数个数是否正确
        if (argc != 3) {
            printf("Usage: ./a.out filename num\n");
            exit(EXIT_FAILURE); // 参数不正确时退出程序
        }
    
        int linenum = atoi(argv[2]); // 将命令行中指定的行号转换为整数
        char buf[4096];               // 定义缓冲区,用于读取文件内容
    
        // 尝试以读写模式打开文件
        FILE *fp = fopen(argv[1], "r+");
        // 如果文件无法打开,打印错误信息并退出程序
        if (!fp) {
            printf("Can't open file %s, file not exist or permission denied!\n", argv[1]);
            exit(EXIT_FAILURE);
        }
    
        int total_line = 0; // 记录文件的总行数
        int size = 0;       // 记录文件的总大小
    
        // 循环读取文件直到文件末尾,计算总行数和总大小
        while (fgets(buf, sizeof(buf), fp)) {  
            size += strlen(buf); // 累加每行的长度
            total_line++;        // 行数加1
        }
    
        // 如果要删除的行数大于文件的总行数,打印错误信息并退出程序
        if (linenum > total_line) {
            printf("%d is greater than total_line!\n", linenum);
            exit(EXIT_FAILURE);
        }
    
        int s = 0; // 记录要删除的行的大小
        int t = 0; // 记录当前读取行的大小
        int i = 0; // 记录当前行数
        fseek(fp, 0L, SEEK_SET); // 将文件指针重置到文件开头
    
        // 再次循环读取文件,准备删除指定行
        while (fgets(buf, sizeof(buf), fp)) {  
            i++; // 当前行数加1
            t = strlen(buf); // 当前行的长度
    
            // 如果当前行是要删除的行,记录其大小
            if (i == linenum) {
                s = t;
            }
    
            // 如果当前行在要删除的行之后,将其前移
            if (i > linenum) {
                fseek(fp, -(s+t), SEEK_CUR); // 将文件指针移动到正确的位置
                fputs(buf, fp);             // 写入当前行
                fseek(fp, s, SEEK_CUR);      // 将文件指针向前移动s个字节
            }
        } 
    
        // 截断文件,删除指定行
        truncate(argv[1], size-s);
        // 关闭文件指针
        fclose(fp);
    
        return 0; // 正常退出程序
    }

    (4) 这三个删除文件指定行的函数都需借助临时文件完成

    1
    通讯等不确定性条件下设计无人机之间的安全距离
    « 上一篇 2024-07-16
    FPGA/数字IC-常考八股
    下一篇 » 2024-06-27

    评论 (0)

    取消