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