Linux之不使用命令删除文件中的第N行
我的学记|刘航宇的博客

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

刘航宇
1年前发布 /正在检测是否收录...

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) 这三个删除文件指定行的函数都需借助临时文件完成

© 版权声明
THE END
喜欢就支持一下吧
点赞 1 分享 赞赏
评论 抢沙发
取消