目录
(一)文件复制(my_cp函数)
(二)文件内容查看(my_cat函数)
(三)切换目录(my_cd函数)
(四)列出目录内容(my_ls函数)
(五)详细列出目录内容(my_ll函数)
(六)创建符号链接(my_symlink函数)
(七)创建硬链接(my_link函数)
(八)删除文件(my_rm函数)
(九)创建空文件(my_touch函数)
(十)移动文件(my_mv函数)
(十一)主函数(main函数)
一、项目概述
本项目是一个使用 C 语言编写的程序,其目的是模拟常见的文件操作命令,为用户提供类似于在操作系统中执行基本文件操作的功能。这些功能包括文件的复制、内容查看、目录切换、目录内容的列出(包括简单列出和详细列出)、创建符号链接和硬链接、文件的删除、创建空文件以及文件的移动等。
二、功能模块设计
(一)文件复制(my_cp
函数)
将指定的源文件内容逐行复制到目标文件中
int my_cp(const char *src_file, const char *dst_file)
{// 以只读模式打开源文件FILE * src_fp = fopen(src_file, "r");// 以写入模式打开目标文件,如果目标文件不存在则创建FILE * dst_fp = fopen(dst_file, "w");// 如果源文件或目标文件打开失败if (NULL == src_fp || NULL == dst_fp){// 打印错误信息perror("fopen");// 返回 1 表示操作失败return 1;}// 定义一个 1024 字节大小的缓冲区char buf[1024] = { 0 };// 进入一个无限循环while (1){// 从源文件中读取一行内容到缓冲区char* s = fgets(buf, sizeof(buf), src_fp);// 如果读取失败(到达文件末尾)if (NULL == s){// 退出循环break;}// 将缓冲区中的内容写入到目标文件fputs(buf, dst_fp);}// 关闭目标文件fclose(dst_fp);// 关闭源文件fclose(src_fp);// 返回 0 表示操作成功return 0;
}
(二)文件内容查看(my_cat
函数)
打开指定的文件,并将其内容逐行打印在终端上
int my_cat(const char *src_file)
{// 以只读模式打开指定文件FILE * fp = fopen(src_file, "r");// 如果文件打开失败if (fp == NULL){// 打印错误信息perror("fopen");}// 定义一个 1024 字节大小的缓冲区char buf[1024] = { 0 };// 进入一个无限循环while (1){// 从文件中读取一行内容到缓冲区char *s = fgets(buf, sizeof(buf), fp);// 如果读取失败(到达文件末尾)if (s == NULL){// 退出循环break;}// 在控制台打印缓冲区中的内容printf("%s", buf);}// 关闭文件fclose(fp);
}
(三)切换目录(my_cd
函数)
实现切换到指定目录,并获取切换后的当前工作目录
void my_cd(const char * src_file)
{// 定义一个 125 字节大小的缓冲区char buf[125] = { 0 };// 切换到指定的目录chdir(src_file);// 获取当前工作目录,并将其存储在缓冲区中getcwd(buf, sizeof(buf));
}
(四)列出目录内容(my_ls
函数)
打开指定的目录,并在终端打印出目录中的文件名
void my_ls(const char *src_file)
{// 打开指定的目录DIR * dir = opendir(src_file);// 如果目录打开失败if (dir == NULL){// 打印错误信息perror("opendir");}// 定义一个 125 字节大小的缓冲区char buf[125] = { 0 };// 进入一个无限循环while (1){// 读取目录中的一个文件或子目录信息struct dirent *info = readdir(dir);// 如果读取失败(到达目录末尾)if (info == NULL){// 退出循环break;}// 在控制台打印文件或子目录的名称printf("%s ", info->d_name);}// 在控制台打印一个换行符printf("\n");// 关闭目录closedir(dir);
}
(五)详细列出目录内容(my_ll
函数)
打开指定目录,并详细地打印出目录中每个文件的各种信息,包括文件类型、权限、所有者、所属组、大小、修改时间和文件名等
void my_ll(const char *src_file)
{// 打开指定的目录DIR * dir = opendir(src_file);// 如果目录打开失败if (dir == NULL){// 打印错误信息perror("opendir");}// 进入一个无限循环while (1){// 读取目录中的一个文件或子目录信息struct dirent * info = readdir(dir);// 如果读取失败(到达目录末尾)if (info == NULL){// 退出循环break;}// 用于存储文件的完整路径struct stat st;// 将文件路径拼接完整char fullPath[512];snprintf(fullPath, sizeof(fullPath), "%s/%s", src_file, info->d_name);// 获取文件的状态信息int ret = stat(fullPath, &st);// 如果获取状态信息失败if (ret == -1){// 打印错误信息perror("stat");}// 根据文件类型进行标记输出if (S_ISREG(st.st_mode)){fputc('-', stdout);}else if (S_ISDIR(st.st_mode)){fputc('d', stdout);}else if (S_ISCHR(st.st_mode)){fputc('c', stdout);}else if (S_ISBLK(st.st_mode)){fputc('b', stdout);}else if (S_ISFIFO(st.st_mode)){fputc('f', stdout);}else if (S_ISLNK(st.st_mode)){fputc('l', stdout);}else if (S_ISSOCK(st.st_mode)){fputc('o', stdout);}// 根据用户权限进行标记输出if (st.st_mode & S_IRUSR){fputc('r', stdout);}else{fputc('-', stdout);}if (st.st_mode & S_IWUSR){fputc('w', stdout);}else{fputc('-', stdout);}if (st.st_mode & S_IXUSR){fputc('x', stdout);}else{fputc('-', stdout);}// 根据组权限进行标记输出if (st.st_mode & S_IRGRP){fputc('r', stdout);}else{fputc('-', stdout);}if (st.st_mode & S_IWGRP){fputc('w', stdout);}else{fputc('-', stdout);}if (st.st_mode & S_IXGRP){fputc('x', stdout);}else{fputc('-', stdout);}// 根据其他用户权限进行标记输出if (st.st_mode & S_IROTH){fputc('r', stdout);}else{fputc('-', stdout);}if (st.st_mode & S_IWOTH){fputc('w', stdout);}else{fputc('-', stdout);}if (st.st_mode & S_IXOTH){fputc('x', stdout);}else{fputc('-', stdout);}// 获取文件的修改时间,并转换为本地时间格式struct tm *t = localtime(&st.st_mtime);// 获取文件所有者的用户信息uid_t uid = st.st_uid;struct passwd * pw = getpwuid(uid);// 如果获取用户信息失败if (pw == NULL){// 打印错误信息perror("getpwuid");}// 获取文件所属组的信息gid_t gid = st.st_gid;struct group * gr = getgrgid(gid);// 如果获取组信息失败if (gr == NULL){// 打印错误信息perror("getgrgid");}// 打印文件的链接数、所有者用户名、所属组名、文件大小、修改月份、日期、时间和文件名printf(" %2lu %-5s %-5s %5lu %02d 月 %d %d:%2d %s\n",st.st_nlink, pw->pw_name, gr->gr_name, st.st_size,t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, info->d_name);}// 关闭目录closedir(dir);
}
(六)创建符号链接(my_symlink
函数)
创建指定源文件到目标文件的符号链接,如果创建过程中出现错误,会打印相应的错误提示。
void my_symlink(const char *src_file, const char *dst_file)
{// 创建符号链接int ret = symlink(src_file, dst_file);// 如果创建失败if (ret == -1){// 打印错误信息perror("symlink");}
}
(七)创建硬链接(my_link
函数)
创建指定源文件到目标文件的硬链接,若创建失败则打印错误信息。
void my_link(const char *src_file, const char *dst_file)
{// 创建硬链接int ret = link(src_file, dst_file);// 如果创建失败if (ret == -1){// 打印错误信息perror("link");}
}
(八)删除文件(my_rm
函数)
删除指定的文件,如果删除操作遇到问题,会给出错误提示。
void my_rm(const char *src_file)
{// 删除指定的文件int ret = remove(src_file);// 如果删除失败if (ret == -1){// 打印错误信息perror("remove");}
}
(九)创建空文件(my_touch
函数)
创建一个空文件,如果在创建过程中出现错误,会打印错误信息。
void my_touch(const char *src_file)
{// 以写入模式打开指定的文件,如果文件不存在则创建FILE * fp = fopen(src_file, "w");// 如果文件打开失败if (fp == NULL){// 打印错误信息perror("fopen");}
}
(十)移动文件(my_mv
函数)
通过先复制文件,然后删除源文件的方式来实现文件的移动操作。
void my_mv(const char *src_file, const char *dst_file)
{// 先调用复制文件的函数my_cp(src_file, dst_file);// 再删除源文件my_rm(src_file);
}
(十一)主函数(main
函数)
主函数的作用是不断获取用户输入的命令和相关参数,根据输入的不同情况(参数数量)调用相应的功能函数来执行具体的文件操作,并处理可能出现的错误。它是整个程序的控制中心,协调各个功能模块的运行,以实现对文件的各种操作模拟
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <time.h>
#include <grp.h>
#include <pwd.h>#include "mycpcatcd.h"
#include "mylsll.h"
#include "order.h"// 函数:打印命令提示符
void printfCommandPrompt()
{// 定义一个 125 字节大小的缓冲区来存储当前工作目录char buf[125] = {0};// 获取当前工作目录并存储在缓冲区中getcwd(buf, sizeof(buf));// 以特定的格式和颜色打印命令提示符的各个部分printf("\33[1m"); // 加粗字体printf("\033[39;32m"); // 绿色文本printf("linux@ubuntu"); // 用户名部分printf("\033[0m"); // 恢复默认颜色和字体printf("\33[1m"); // 加粗字体printf(":"); // 分隔符printf("\033[0m"); // 恢复默认颜色和字体printf("\33[1m"); // 加粗字体printf("\033[39;34m"); // 蓝色文本printf("%s", buf); // 打印当前工作目录printf("\033[0m"); // 恢复默认颜色和字体printf("\33[1m"); // 加粗字体printf("$ "); // 命令输入提示符printf("\033[0m"); // 恢复默认颜色和字体
}// 主函数
int main()
{while (1) // 无限循环,等待用户不断输入命令{printfCommandPrompt(); // 打印命令提示符// 定义字符数组来存储命令、源文件和目标文件char order[100] = {0};char src_file[100] = {0};char dst_file[100] = {0};char orders[100] = {0};fgets(orders, sizeof(orders), stdin); // 从标准输入获取用户输入的命令行int i = 0; int count = 0;// 计算用户输入命令中的空格数量while (orders[i]!= '\0'){if (orders[i] ==''){count++;}i++;}char *token = NULL; // 用于存储分割后的字符串// 根据空格数量进行不同的处理switch(count){case 0: // 用户输入没有空格token = strtok(orders,"\n"); // 提取命令,去除换行符strcpy(order,token); // 将提取的命令复制到 order 数组// 根据命令执行相应操作if (strcmp("cd",order) == 0 ) // 如果是切换目录命令{chdir("/home/linux"); // 切换到指定目录}else if (strcmp("ls",order) == 0 ) // 如果是列出目录命令{my_ls("./"); // 列出当前目录内容}else if (strcmp("ll",order) == 0 ) // 如果是详细列出目录命令{my_ll("./"); // 详细列出当前目录内容}else if (strcmp("q",order) == 0 ) // 如果是退出命令{return 0; // 程序结束}else // 如果输入的命令不匹配以上已知命令{printf("Input error, please re-enter\n"); // 提示输入错误,重新输入}break;case 1: // 用户输入有一个空格token = strtok(orders," "); // 提取命令strcpy(order,token); // 复制命令token = strtok(NULL,"\n"); // 提取源文件路径strcpy(src_file,token); // 复制源文件路径// 根据命令和源文件路径执行相应操作if (strcmp("cat",order) == 0 ) // 如果是查看文件内容命令{my_cat(src_file); // 查看指定文件内容}else if (strcmp("cd",order) == 0 ) // 如果是切换目录命令{my_cd(src_file); // 切换到指定目录}else if (strcmp("ls",order) == 0 ) // 如果是列出目录命令{my_ls(src_file); // 列出指定目录内容}else if (strcmp("ll",order) == 0 ) // 如果是详细列出目录命令{my_ll(src_file); // 详细列出指定目录内容}else if (strcmp("rm",order) == 0 ) // 如果是删除文件命令{my_rm(src_file); // 删除指定文件}else if (strcmp("touch",order) == 0) // 如果是创建空文件命令{my_touch(src_file); // 创建指定空文件}else // 如果输入的命令不匹配以上已知命令{printf("Input error, please re-enter\n"); // 提示输入错误,重新输入}break;case 2: // 用户输入有两个空格token = strtok(orders," "); // 提取命令strcpy(order,token); // 复制命令token = strtok(NULL," "); // 提取源文件路径strcpy(src_file,token); // 复制源文件路径token = strtok(NULL,"\n"); // 提取目标文件路径strcpy(dst_file,token); // 复制目标文件路径// 根据命令、源文件路径和目标文件路径执行相应操作if (strcmp("cp",order) == 0 ) // 如果是复制文件命令{my_cp(src_file,dst_file); // 复制文件}else if (strcmp("ln",order) == 0 ) // 如果是创建硬链接命令{my_link(src_file,dst_file); // 创建硬链接}else if (strcmp("mv",order) == 0 ) // 如果是移动文件命令{my_mv(src_file,dst_file); // 移动文件}else // 如果输入的命令不匹配以上已知命令{printf("Input error, please re-enter\n"); // 提示输入错误,重新输入}break;case 3: // 用户输入有三个空格token = strtok(orders," "); // 提取命令的前部分strcpy(order,token); // 复制token = strtok(NULL," "); // 提取命令的后部分,与前部分拼接strcat(order,token); // 拼接命令token = strtok(NULL," "); // 提取源文件路径strcpy(src_file,token); // 复制源文件路径token = strtok(NULL,"\n"); // 提取目标文件路径strcpy(dst_file,token); // 复制目标文件路径// 如果是创建符号链接命令if ( strcmp("ln-s",order) == 0 ){my_symlink(src_file,dst_file); // 创建符号链接}else // 如果输入的命令不匹配创建符号链接命令{printf("Input error, please re-enter\n"); // 提示输入错误,重新输入}break;default: // 用户输入的空格数量不符合预期printf("Input error!"); // 提示输入错误}}return 0;
}
三、数据结构
• 字符数组:如 buf、order、src_file、dst_file 等,用于存储命令、文件路径等字符串信息。
四、错误处理
在每个文件操作函数中,如果文件打开、读取、写入或其他操作失败,都会通过 perror 函数打印出相应的错误信息,以便用户了解操作失败的原因。
五、运行环境
• 操作系统:支持常见的操作系统,如 Windows、Linux 等。
• 编译环境:需要 C 语言编译器,如 GCC 等。
六、测试计划
• 对每个功能函数进行单独的单元测试,输入不同的有效和无效参数,检查返回值和输出结果是否符合预期。
◦ 例如,对于文件复制函数 my_cp,测试不同大小、类型的文件,包括空文件、大文件、文本文件、二进制文件等。
◦ 对于目录操作函数,如 my_ls 和 my_ll,测试不同权限、不同内容的目录。
• 进行综合测试,模拟用户输入各种命令和参数组合,检查程序的整体运行情况。
◦ 包括连续执行多个命令、输入错误的命令格式、在不同的目录下执行命令等场景。
七、维护与扩展
• 未来可以根据需要添加更多的文件操作命令或优化现有功能的性能。
◦ 例如,添加文件重命名功能、支持文件压缩和解压缩等。
• 对错误处理进行进一步的完善,提供更友好的错误提示信息。
◦ 比如,对于一些常见的错误,给出更具体的解决建议。