阶段练习——minishell

目录

(一)文件复制(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,测试不同权限、不同内容的目录。
• 进行综合测试,模拟用户输入各种命令和参数组合,检查程序的整体运行情况。
◦ 包括连续执行多个命令、输入错误的命令格式、在不同的目录下执行命令等场景。

七、维护与扩展
• 未来可以根据需要添加更多的文件操作命令或优化现有功能的性能。
◦ 例如,添加文件重命名功能、支持文件压缩和解压缩等。
• 对错误处理进行进一步的完善,提供更友好的错误提示信息。
◦ 比如,对于一些常见的错误,给出更具体的解决建议。

整体框图

主函数流程图 

  • 27
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值