目录
一、项目概述
本项目是一个使用 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,测试不同权限、不同内容的目录。
• 进行综合测试,模拟用户输入各种命令和参数组合,检查程序的整体运行情况。
◦ 包括连续执行多个命令、输入错误的命令格式、在不同的目录下执行命令等场景。
七、维护与扩展
• 未来可以根据需要添加更多的文件操作命令或优化现有功能的性能。
◦ 例如,添加文件重命名功能、支持文件压缩和解压缩等。
• 对错误处理进行进一步的完善,提供更友好的错误提示信息。
◦ 比如,对于一些常见的错误,给出更具体的解决建议。