实现基础的shell程序

1. 实现一个基础的 shell 程序,主要完成两个命令的功能 cp 和 ls

1.1.1. cp 命令主要实现:
  • ⽂件复制
  • ⽬录复制
1.1.2. ls 命令主要实现:
  • ls -l 命令的功能

1.1. 在框架设计上,采⽤模块化设计思想,并具备⼀定的可扩展性, 具体框架如下:

  • cmd_handle 模块: 用于解析命令相关信息,并进行命令的分发执行
  • cmd_ls 模块 : 用于执行 ls 命令
  • cmd_cp 模块 : 用于执行 cp 命令
  • cmd_xxx 模块 : 用于扩展

  • 编写MakeFile
OBJS :=main.o cmd_ls.o cmd_cp.o cdm_handle.o  #	依赖的文件
CC :=gcc  #	编译器
TARGET :=tinshell  # 目标文件

%.o: %.c
	$(CC) -c $< -o $@

$(TARGET): $(OBJS)
	$(CC) $^ -o $@  
	@echo "目标文件编译成功."  # 编译成功提示
 
clean: #删除编译生成的文件
	rm -rf $(OBJS) $(TARGET)
	@echo "文件删除成功."  # 删除成功提示
  • main函数:
#include <stdio.h>
#include <string.h>

#include "cmd_handle.h"
#define SZ_CMD 64
int main()
{
    char command[SZ_CMD] = {0};

    while (1)
    {
        printf("请输入需要操作的shell指令: ");

        fgets(command, SZ_CMD, stdin);       // 读取用户输入的指令  stdin标准输入
        command[strlen(command) - 1] = '\0'; // 去掉换行符

        if (strcmp(command, "exit") == 0)  // 判断是否为退出指令
        {
            printf("退出程序\n");
            break;
        }
        else
        {
            printf("执行指令: %s\n", command);    // 打印用户输入的指令
            cmd_execute(command);   //调用cmd_handle模块中的函数,去执行指令
        }
    }

    return 0;
}
  • cmd_handle.h
#ifndef  __CMD_HANDLE_H__
#define  __CMD_HANDLE_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

extern int cmd_execute(char *cmd_str);  // 执行命令


#endif
  • cmd_handle.c
  • 注意:一般在工作开发中 会有两个版本一个是DEBUG版本,还有一个是RELEASE版本,
    • DEBUG版本是开发是调试用的,
    • RELEASE版本是发布是用的
#include "cmd_handle.h"

#define DEBUG 
int cmd_execute(char *cmd_str)
{
#ifdef DEBUG
    printf("[DEBUG]cmd_str:< %s >\n", cmd_str);

#endif
    return 0;
}

逐行解释

  1. #include "cmd_handle.h"
    • 这行代码的意思是:把这个文件的内容包含进来。这个文件里可能有一些我们需要用到的定义,比如函数的声明等。cmd_handle.h
  1. #define DEBUG
    • 这行代码的意思是:定义一个叫做的宏。宏是一种在编译前进行的文本替换。这里定义的目的是为了在代码中加入一些调试信息,帮助我们更好地理解程序的运行情况。DEBUGDEBUG
  1. int cdm_handle(char *cmd_str)
    • 这行代码定义了一个函数,函数的名字叫。这个函数接受一个参数,参数的名字叫,是一个指向字符的指针,用来传递一个字符串。函数的返回值是一个整数()。cdm_handlecmd_strint
  1. #ifdef DEBUG
    • 这行代码的意思是:如果定义了宏,那么接下来的代码块就会被编译。这是一种条件编译的指令,用于控制某些代码是否会被编译进最终的程序中。DEBUG
  1. printf("cmd_str:%s\n", cmd_str);
    • 这行代码的意思是:输出一个字符串,字符串的内容是变量的值。是一个占位符,用来表示字符串。是一个换行符,用来在输出后换行。这行代码的作用是打印出传入的命令字符串,帮助我们调试程序。cmd_str%s\n
  1. #endif
    • 这行代码的意思是:条件编译的结束标记。它表示前面的开始的代码块到此结束。#ifdef DEBUG
  1. return 0;
    • 这行代码的意思是:函数返回0。在C语言中,返回0通常表示函数执行成功。

总结

这段代码定义了一个函数,它接受一个字符串参数。如果定义了宏,函数会打印出这个字符串,然后返回0。这个函数的目的是处理一个命令字符串,并在调试模式下输出这个字符串。cdm_handlecmd_strDEBUG

示例

假设你有一个主函数,调用函数:maincdm_handle

#include "cmd_handle.h"

#include <stdio.h>

int main() {

char *command = "ls -l";

cdm_handle(command);

return 0;}

int cmd_execute(char *cmd_str){

#ifdef DEBUG

printf("[DEBUG]cmd_str:< %s >\n", cmd_str);

#endif

return 0;}

编译并运行这个程序:

gcc -o main main.c cmd_handle.c

./main

如果宏被定义,你会看到输出:DEBUG

cmd_str:ls -l

这表示函数成功地打印了传入的命令字符串cdm_handle

1.2. strtok函数:

函数原型

char *strtok(char *str, const char *delim);

  • 参数
    • str:要分割的字符串。第一次调用时传入要分割的字符串,之后传入 。NULL
    • delim:分隔符字符串,包含所有用作分隔符的字符。
  • 返回值
    • 返回下一个分割后的字符串。
    • 如果没有更多的字符串可返回,则返回 。NULL

使用方法

  1. 第一次调用
    • 传入要分割的字符串 和分隔符字符串 。strdelim
    • strtok 会返回第一个分割后的字符串,并在内部保存下一个分割点的位置。
  1. 后续调用
    • 传入 和分隔符字符串 。NULLdelim
    • strtok 会从上次保存的位置继续分割字符串,返回下一个分割后的字符串。
    • 重复调用,直到返回 ,表示没有更多的字符串可分割。NULL

demo:

#include <stdio.h>  // 包含标准输入输出库,用于printf等函数
#include <string.h> // 包含字符串处理函数,用于strtok等函数

int main()  // 主函数,程序的入口点
{
    char str[100] = "ABC 123 XYZ";  // 定义一个长度为100的字符数组str,并初始化为"ABC 123 XYZ"
    char *first = NULL;  // 定义一个指向字符的指针first,初始化为NULL
    char *other = NULL;  // 定义一个指向字符的指针other,初始化为NULL
    first = strtok(str, " ");  // 使用strtok函数按空格分隔str,返回第一个分隔后的字符串,存储在first中

    if (NULL == first)  // 检查first是否为NULL,如果是,表示没有找到任何分隔后的字符串
    {
        printf("No token found\n");  // 输出错误信息
        return -1;  // 返回-1表示错误
    }
    printf("first:%s\n", first);  // 输出第一个分隔后的字符串

    while ((other = strtok(NULL, " ")) != NULL)  // 继续使用strtok函数按空格分隔字符串,返回后续的分隔后的字符串,存储在other中
    {
        printf("other:%s\n", other);  // 输出每个后续的分隔后的字符串
    }

    return 0;  // 返回0表示程序正常结束
}

运行结果:

1.3. cmd_handle.c 函数

#include "cmd_handle.h"

#define DEBUG
int command_execute(char *cmd_str)
{
    cmd_t command;  // 定义命令结构体
    int ret;
    if (NULL == cmd_str)    return -1;

#ifdef DEBUG
    printf("[DEBUG]cmd_str:< %s >\n", cmd_str); // 打印命令字符串

#endif
    init_command_struct(&command); // 初始化命令结构体

#ifdef DEBUG
    ret = command_parse(cmd_str, &command);  // 解析命令
    if (ret == -1)	return -1;
#endif

#ifdef DEBUG
    print_command_table(&command); // 打印命令结构体

#endif
    ret = cmd_dispatch(&command); // 分发命令
    if (ret == -1)  return -1;
    
    return 0;
}

void init_command_struct(cmd_t *pcmd) // 初始化命令结构体
{
    memset(pcmd->cmd_name, 0, SZ_NAME); // 清空命令名称
    for (int i = 0; i < SZ_COUNT; i++)
    {
        memset(pcmd->cmd_arg_list[i], 0, SZ_ARG); // 清空命令参数列表
    }

    pcmd->cmd_arg_count = 0; // 清空命令参数个数
}
// cp 1.txt 2.txt
int command_parse(char *cmd_str, cmd_t *pcmd) // 解析命令
{
    char *p_cmd_name = NULL;
    char *p_cmd_arg = NULL;
    int i = 0 ,j = 0;
    if (NULL == cmd_str || NULL == pcmd)
        return -1;
    p_cmd_name = strtok(cmd_str, " "); // 获取命令名称
#ifdef DEBUG

    strcpy(pcmd->cmd_name, p_cmd_name);
    printf("[DEBUG]:命令名称 : %s\n", pcmd->cmd_name);
#endif
    if (NULL == p_cmd_name)
    {
        printf("No token found\n");
        return -1;
    }
    while ((p_cmd_arg = strtok(NULL, " ")) != NULL)  // 获取命令参数
    {

        printf("第%d个命令参数:%s\n",++j, p_cmd_arg);
        strcpy(pcmd->cmd_arg_list[i++], p_cmd_arg);
        pcmd->cmd_arg_count = i;
    }

    return 0;
}

void print_command_table(cmd_t *pcmd)  // 打印命令结构体
{
    printf("----------------\n");

    printf("[DEBUG]解析之后的命令名称: < %s >\n", pcmd->cmd_name);
    printf("[DEBUG]解析之后的命令参数个数: < %d >\n", pcmd->cmd_arg_count);
    printf("[DEBUG]解析之后的命令内容是: ");
    for (int i = 0; i < pcmd->cmd_arg_count; i++)
    {
        printf("< %s > ", pcmd->cmd_arg_list[i]);
    }

    printf("\n-----------------\n");
}

int cmd_dispatch(cmd_t *pcmd) // 分发命令
{
    if (pcmd == NULL)   return -1;

    if (strcmp(pcmd->cmd_name, "ls") == 0)
    {
#ifdef DEBUG
        printf("[DEBUG]执行ls命令 \n");
#endif
    }
    else if (strcmp(pcmd->cmd_name, "cp") == 0)
    {
#ifdef DEBUG
        printf("[DEBUG]执行cp命令\n");
        cmd_cp_execute(pcmd);
#endif
    }
    return 0;
}

2. 命令处理框架设计 (⼀)

输入的命令是1个完整字符串,比如复制 ”cp test.txt test1.txt“,在实际实现业务逻辑时需要进⾏拆分

具体在解析字符串的步骤如下:

  • step 1 : 设计⾃定义的数据结构存储拆分之后的命名信息
  • step 2 : 使⽤ strtok 函数对命令字符串进⾏拆分, 并存储到⾃定义数据结构中
  • step 3 : 按照命令名字分发到具体模块中执行

对于解析之后的字符串,需要保存到自定义的数据结构中 :

  • 命令名称
  • 参数个数
  • 参数列表

  • cmd_handle.h
#ifndef  __CMD_HANDLE_H__
#define  __CMD_HANDLE_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SZ_NAME 8  // 
#define SZ_ARG 32
#define SZ_COUNT 2 

typedef struct command{
 char cmd_name[SZ_NAME];  // 命令名称
 char cmd_arg_list[SZ_COUNT][SZ_ARG];   // 命令参数列表  二维数组
 int cmd_arg_count;   // 命令参数个数
}cmd_t;

extern void init_command_struct(cmd_t *pcmd); // 初始化命令表
extern void print_command_table(cmd_t *pcmd); // 打印命令表

extern int command_execute(char *cmd_str);  // 执行命令
extern int command_parse(char *cmd_str, cmd_t *pcmd);  // 解析命令
extern int cmd_dispatch(cmd_t *pcmd);  // 分发命令
#endif
  • cmd_handle.c
#include "cmd_handle.h"  // 包含命令处理相关的头文件
#include "cmd_cp.h"      // 包含cp命令处理相关的头文件

#define DEBUG  // 定义DEBUG宏,用于控制调试信息的输出

int command_execute(char *cmd_str)  // 命令执行函数
{
    cmd_t command;  // 定义一个命令结构体变量
    int ret;  // 定义返回值变量
    if (NULL == cmd_str)  // 检查输入字符串是否为NULL
        return -1;  // 如果为NULL,返回-1表示错误

#ifdef DEBUG  // 如果定义了DEBUG宏
    printf("[DEBUG]cmd_str:< %s >\n", cmd_str);  // 输出调试信息,显示输入的命令字符串
#endif

    init_command_struct(&command);  // 调用函数初始化命令结构体

#ifdef DEBUG  // 如果定义了DEBUG宏
    ret = command_parse(cmd_str, &command);  // 调用函数解析命令字符串
    if (ret == -1)  // 如果解析失败
        return -1;  // 返回-1表示错误
#endif

#ifdef DEBUG  // 如果定义了DEBUG宏
    print_command_table(&command);  // 调用函数打印命令结构体的内容
#endif

    ret = cmd_dispatch(&command);  // 调用函数分发命令
    if (ret == -1)  // 如果分发失败
        return -1;  // 返回-1表示错误

    return 0;  // 返回0表示成功
}

void init_command_struct(cmd_t *pcmd)  // 初始化命令结构体的函数
{
    int i;  // 定义循环变量
    memset(pcmd->cmd_name, 0, SZ_NAME);  // 清空命令名称
    for (i = 0; i < SZ_COUNT; i++)  // 循环清空命令参数列表
    {
        memset(pcmd->cmd_arg_list[i], 0, SZ_ARG);  // 清空每个命令参数
    }

    pcmd->cmd_arg_count = 0;  // 清空命令参数个数
}

int command_parse(char *cmd_str, cmd_t *pcmd)  // 命令解析函数
{
    char *p_cmd_name = NULL;  // 定义指向命令名称的指针
    char *p_cmd_arg = NULL;  // 定义指向命令参数的指针
    int i = 0;  // 定义命令参数索引
    if (NULL == cmd_str || NULL == pcmd)  // 检查输入字符串和命令结构体指针是否为NULL
        return -1;  // 如果为NULL,返回-1表示错误
    p_cmd_name = strtok(cmd_str, " ");  // 使用strtok分割命令字符串,获取命令名称

#ifdef DEBUG  // 如果定义了DEBUG宏
    printf("p_cmd_name:%s\n", p_cmd_name);  // 输出调试信息,显示命令名称
    strcpy(pcmd->cmd_name, p_cmd_name);  // 复制命令名称到命令结构体
    printf("[DEBUG]: cmd_name : %s\n", pcmd->cmd_name);  // 输出调试信息,显示命令名称
#endif

    if (NULL == p_cmd_name)  // 检查是否获取到命令名称
    {
        printf("No token found\n");  // 如果没有获取到,输出错误信息
        return -1;  // 返回-1表示错误
    }
    while ((p_cmd_arg = strtok(NULL, " ")) != NULL)  // 继续使用strtok分割命令字符串,获取命令参数
    {
        printf("p_cmd_arg:%s\n", p_cmd_arg);  // 输出调试信息,显示命令参数
        strcpy(pcmd->cmd_arg_list[i++], p_cmd_arg);  // 复制命令参数到命令结构体
        pcmd->cmd_arg_count = i;  // 更新命令参数个数
    }

    return 0;  // 返回0表示成功
}

void print_command_table(cmd_t *pcmd)  // 打印命令结构体内容的函数
{
    printf("----------------\n");  // 输出分隔线

    printf("[DEBUG]cmd_name: < %s >\n", pcmd->cmd_name);  // 输出命令名称
    printf("[DEBUG]cmd_arg_count: < %d >\n", pcmd->cmd_arg_count);  // 输出命令参数个数
    printf("[DEBUG]cmd_arg_list: ");  // 输出命令参数列表前缀
    for (int i = 0; i < pcmd->cmd_arg_count; i++)  // 循环输出每个命令参数
    {
        printf("< %s > ", pcmd->cmd_arg_list[i]);  // 输出命令参数
    }

    printf("\n-----------------\n");  // 输出分隔线
}

int cmd_dispatch(cmd_t *pcmd)  // 命令分发函数
{
    if (pcmd == NULL)  // 检查命令结构体指针是否为NULL
        return -1;  // 如果为NULL,返回-1表示错误

    if (strcmp(pcmd->cmd_name, "ls") == 0)  // 检查命令名称是否为"ls"
    {
#ifdef DEBUG  // 如果定义了DEBUG宏
        printf("[DEBUG]执行ls命令 \n");  // 输出调试信息,表示执行ls命令
#endif
    }
    else if (strcmp(pcmd->cmd_name, "cp") == 0)  // 检查命令名称是否为"cp"
    {
#ifdef DEBUG  // 如果定义了DEBUG宏
        printf("[DEBUG]执行cp命令\n");  // 输出调试信息,表示执行cp命令
        cmd_cp_execute(pcmd);  // 调用cp命令执行函数
#endif
    }
    return 0;  // 返回0表示成功
}
  • main.c
#include <stdio.h>
#include <string.h>

#include "cmd_handle.h"
#define SZ_CMD 64

int main()
{
    char command[SZ_CMD] = {0};

    while (1)
    {
        printf("请输入需要操作的shell指令: ");

        fgets(command, SZ_CMD, stdin);       // 读取用户输入的指令  stdin标准输入
        command[strlen(command) - 1] = '\0'; // 去掉换行符

        if (strcmp(command, "exit") == 0)  // 判断是否为退出指令
        {
            printf("退出程序\n");
            break;
        }
        else
        {
            printf("执行指令: %s\n", command);    // 打印用户输入的指令
            command_execute(command);   //调用cmd_handle模块中的函数,去执行指令
        }
    }

    return 0;
}
  • 运行结果:

2.1. cp 命令设计与实现 (⼀) _保持路径

  • 完成⼀个⽬录的复制,具体要求如下:
    • 实现⽂件复制
      • cp 1.txt 2.txt
    • 实现⽬录复制
      • cp src_dir dest_dir
  • 总体思路 :
    • 根据⽂件类型进⾏判断,如果是普通⽂件,则直接进⾏复制,
    • 如果是⽬录,则递归复制⽬录
  • 基本思路如下:
    • 判断⽂件类型
      • 是普通⽂件, 则直接进⾏复制
      • 是⽬录,则递归进⾏⽬录复制
    • 复制⽬录
      • 在⽬标路径创建新的同名⽬录
      • 打开⽬录
      • 遍历⽬录
        • 获取⽂件名,并合成源⽬录绝对路径以及⽬标⽬录绝对路径
        • 根据路径判断源⽂件类型
          • 是⽂件,则直接进⾏复制
          • 是⽬录,则继续进⾏递归复制

  • cp 的命令的总的⼊⼝函数为 cmd_cp_execute 函数, 具体逻辑如下:

  • 解析路径就是将 cp 命令参数的参数信息存储到 ⽂件信息结构中
  • cmd_cp.h
#ifndef __CMD_CP_H__
#define __CMD_CP_H__
#include <stdio.h>  // 包含标准输入输出库
#include <stdlib.h>  // 包含标准库,用于退出程序等
#include <string.h>  // 包含字符串处理函数
#include <unistd.h>  // 包含 POSIX 操作系统 API
#include <sys/types.h>  // 包含系统数据类型
#include <sys/stat.h>  // 包含文件状态处理函数
#include <dirent.h>  // 包含目录操作函数
#include <errno.h>
#include "cmd_handle.h"

#define SZ_PATH 1024
#define SZ_BUFFER  1024

typedef enum file_type   // enum 文件类型枚举
{  
    FT_DIR = 0,        // 目录
    FT_FILE = 1,       // 文件
    FT_ERROR = 2,      // 错误
    FT_UNKNOWN = 3     // 文件类型未知
} file_type_t;

typedef struct cp_file_info
{
    file_type_t src_ftype; // 源文件类型
    
    char src_path[SZ_PATH];   // 源文件路径
    char dest_path[SZ_PATH];  // 目标文件路径
} cp_file_info_t;

extern int cmd_cp_execute(cmd_t *pcmd);
#endif
  • cmd_cp.c
#include "cmd_cp.h"

#define DEBUG

int cmd_cp_execute(cmd_t *pcmd)
{
    if (NULL == pcmd) return -1;
    int ret = 0;
#ifdef DEBUG
   
#endif
    cp_file_info_t fileninfo;  // 定义一个cp_file_info_t结构体

    // 解析路径并且保存到 cp_file_info_t结构体中;
    ret = cmd_cp_parse_path(pcmd, &fileninfo);
    if (ret == -1)
        return -1;

    return 0;
}

int cmd_cp_parse_path(cmd_t *pcmd, cp_file_info_t *pcpinfo)
{
    if (NULL == pcmd || NULL == pcpinfo)
        return -1;

    strcpy(pcpinfo->src_path, pcmd->cmd_arg_list[0]);
    strcpy(pcpinfo->dest_path, pcmd->cmd_arg_list[1]);

#ifdef DEBUG
    printf("[DEBUG] 源路径是 :%s\n", pcpinfo->src_path);
    printf("[DEBUG] 目标路径是:%s\n", pcpinfo->dest_path);
#endif
    return 0;
}

2.2. cp 命令设计与实现 (⼆)_解析文件属性

基本思路:

step 1 : 调⽤ stat 函数,获取⽂件属性, 并会保存到 struct stat 结构体中

step 2 : 将⽂件属性中的⽂件类型信息保存到⾃定义结构体中 struct cp_file_info

获取⽂件属性⼀般使⽤ stat 函数 与 lstat 函数

stat 函数适⽤于通⽤⽂件

stat 专⻔针对 链接⽂件

stat 函数

函数头⽂件

#include <sys/types.h>

#include <sys/stat.h>

#include <unistd.h>

函数功能

获取⽂件属性,并将⽂件属性信息保存到 struct stat 结构体中

函数原型

int stat(const char *pathname, struct stat *statbuf);

函数参数

pathname : ⽂件绝对路径

statbuf : ⽂件属性结构体的指针, 具体定义如下

struct stat {
    dev_t     st_dev;     /* 设备 ID,表示文件所在的设备 */
    ino_t     st_ino;     /* inode 编号,每个文件或目录在文件系统中都有一个唯一的 inode 编号 */
    mode_t    st_mode;    /* 文件类型和权限模式,可以通过 S_ISDIR、S_ISREG 等宏来判断文件类型 */
    nlink_t   st_nlink;   /* 硬链接数,表示有多少个文件名指向同一个 inode */
    uid_t     st_uid;     /* 文件所有者的用户 ID */
    gid_t     st_gid;     /* 文件所有者的组 ID */
    dev_t     st_rdev;    /* 设备 ID(如果文件是特殊文件,如字符设备或块设备) */
    off_t     st_size;    /* 文件的总大小,以字节为单位 */
    blksize_t st_blksize; /* 文件系统 I/O 的块大小 */
    blkcnt_t  st_blocks;  /* 分配给文件的 512 字节块的数量 */
    struct timespec st_atime;  /* 最后访问时间 */
    struct timespec st_mtime;  /* 最后修改时间 */
    struct timespec st_ctime;  /* 最后状态改变时间(如权限或所有者更改) */
};

字段详细解释

st_dev:

类型:dev_t

描述:文件所在的设备的 ID。每个文件系统都有一个唯一的设备 ID,表示文件存储在哪个设备上。

st_ino:

类型:ino_t

描述:文件的 inode 编号。inode 是文件系统中用于存储文件元数据的数据结构,每个文件或目录都有一个唯一的 inode 编号。

st_mode:

类型:mode_t

描述:文件类型和权限。可以通过以下宏来检查文件类型:

S_ISDIR(st_mode):是否为目录。

S_ISREG(st_mode):是否为普通文件。

S_ISLNK(st_mode):是否为符号链接。

S_ISCHR(st_mode):是否为字符设备文件。

S_ISBLK(st_mode):是否为块设备文件。

S_ISFIFO(st_mode):是否为 FIFO(命名管道)。

S_ISSOCK(st_mode):是否为套接字。

权限部分可以通过位运算符来检查,例如:

S_IRUSR:用户读权限。

S_IWUSR:用户写权限。

S_IXUSR:用户执行权限。

st_nlink:

类型:nlink_t

描述:文件的硬链接数。硬链接是指向同一个 inode 的多个文件名,这个字段表示有多少个硬链接指向该文件。

st_uid:

类型:uid_t

描述:文件所有者的用户 ID。表示拥有该文件的用户。

st_gid:

类型:gid_t

描述:文件所有者的组 ID。表示拥有该文件的用户组。

st_rdev:

类型:dev_t

描述:如果文件是特殊文件(如设备文件),则存储设备 ID。对于普通文件和目录,这个字段通常没有意义。

st_size:

类型:off_t

描述:文件的大小,以字节为单位。对于普通文件,表示文件的实际大小;对于目录,通常为 0。

st_blksize:

类型:blksize_t

描述:文件系统 I/O 的块大小。这是文件系统建议的 I/O 操作的块大小。

st_blocks:

类型:blkcnt_t

描述:分配给文件的 512 字节块的数量。表示文件实际占用的磁盘块数。

st_atime:

类型:struct timespec

描述:文件的最后访问时间。struct timespec 包含秒和纳秒两个字段,表示时间的高精度。

st_mtime:

类型:struct timespec

描述:文件的最后修改时间。表示文件内容最后一次被修改的时间。

st_ctime:

类型:struct timespec

描述:文件的最后状态改变时间。表示文件的元数据(如权限、所有者等)最后一次被修改的时间。

2.2.1. stat函数demo.c
#include <stdio.h>          // 包含标准输入输出库,用于 printf 和 perror 等函数
#include <sys/types.h>      // 包含系统类型定义,如 dev_t、ino_t 等
#include <sys/stat.h>       // 包含 stat 结构体和相关宏定义
#include <unistd.h>         // 包含标准符号常量和类型定义,如 stat 函数

int main(int argc, char *argv[])  // 主函数,程序入口
{
    struct stat buf;              // 定义一个 struct stat 类型的变量 buf,用于存储文件状态信息
    int ret;                      // 定义一个整型变量 ret,用于存储 stat 函数的返回值

    // 获取文件状态信息
    ret = stat("./text.txt", &buf);  // 调用 stat 函数获取文件 "./text.txt" 的状态信息,存储到 buf 中
    if (ret == -1)                  // 检查 stat 函数是否成功
    {
        perror("stat");             // 如果失败,输出错误信息
        return -1;                  // 返回 -1 表示程序失败
    }

    // 打印文件大小
    printf("文件大小:%ld 字节\n", buf.st_size);  // 打印文件的大小,单位为字节

    // 判断并打印文件类型
    printf("文件类型:");  // 提示输出文件类型
    if (S_ISREG(buf.st_mode))       // 检查文件是否为普通文件
    {
        printf("普通文件\n");       // 如果是普通文件,输出 "普通文件"
    }
    else if (S_ISDIR(buf.st_mode))  // 检查文件是否为目录
    {
        printf("目录\n");           // 如果是目录,输出 "目录"
    }
    else if (S_ISLNK(buf.st_mode))  // 检查文件是否为符号链接
    {
        printf("符号链接\n");       // 如果是符号链接,输出 "符号链接"
    }
    else if (S_ISCHR(buf.st_mode))  // 检查文件是否为字符设备文件
    {
        printf("字符设备文件\n");   // 如果是字符设备文件,输出 "字符设备文件"
    }
    else if (S_ISBLK(buf.st_mode))  // 检查文件是否为块设备文件
    {
        printf("块设备文件\n");     // 如果是块设备文件,输出 "块设备文件"
    }
    else if (S_ISFIFO(buf.st_mode)) // 检查文件是否为 FIFO(命名管道)
    {
        printf("FIFO(命名管道)\n"); // 如果是 FIFO,输出 "FIFO(命名管道)"
    }
    else if (S_ISSOCK(buf.st_mode)) // 检查文件是否为套接字
    {
        printf("套接字\n");         // 如果是套接字,输出 "套接字"
    }
    else
    {
        printf("未知类型\n");       // 如果文件类型未知,输出 "未知类型"
    }

    return 0;  // 程序成功结束,返回 0
}

运行结果:

S_ISREG(buf.st_mode)
宏定义:S_ISREG 是一个标准的宏,用于检查文件是否为普通文件。
作用:如果 buf.st_mode 表示的是一个普通文件,S_ISREG(buf.st_mode) 返回非零值(通常是 1),否则返回 0。
if (S_ISREG(buf.st_mode))
{
printf("普通文件\n");
}
如果文件是一个普通文件,程序会输出 "普通文件"。
2. S_ISDIR(buf.st_mode)
宏定义:S_ISDIR 是一个标准的宏,用于检查文件是否为目录。
作用:如果 buf.st_mode 表示的是一个目录,S_ISDIR(buf.st_mode) 返回非零值(通常是 1),否则返回 0。
else if (S_ISDIR(buf.st_mode))
{
printf("目录\n");
}
如果文件是一个目录,程序会输出 "目录"。

  • demo
在具体业务逻辑实现时,步骤如下 : step 1 : 获取⽂件类型, 并转换成枚举 
file_type_t get_file_type(const char *src_path)
{
    int ret = 0;
    struct stat statbuf;
    ret = stat(src_path, &statbuf);
    if (ret == -1)
    {
        perror("stat():");
        return FT_ERROR;
    }
    if (S_ISDIR(statbuf.st_mode)) // 目录
        return FT_DIR; 

    if (S_ISREG(statbuf.st_mode))  // 文件
        return FT_FILE;

    return FT_UNKNOWN;// 未知
}
step 2 : 将获取的⽂件类型存储⾃定义存储结构中 
int cmd_cp_parse_type(cp_file_info_t *pcpinfo)
{
    enum file_type ftpye;
    ftpye = get_file_type(pcpinfo->src_path);  // 得到文件类型
    if (ftpye == FT_ERROR || ftpye == FT_UNKNOWN)
    {
        perror("get_file_type():");
        return -1;
    }
    else
        pcpinfo->src_ftype = ftpye; //保存文件类型至文件类型枚举结构体中
#ifdef DEBUG
    if (pcpinfo->src_ftype == FT_DIR)
        printf("[DEBUG]src is dir\n"); // 目录

    else if (pcpinfo->src_ftype == FT_FILE)
        printf("[DEBUG]src is file\n"); // 文件
#endif
    return 0;
}
step 3 : 在主逻辑函数 cmd_cp_execute 中进⾏调⽤ 


// 主函数
int cmd_cp_execute(cmd_t *pcmd)
{
    if (NULL == pcmd) return -1;
    int ret = 0;
#ifdef DEBUG
   
#endif
    cp_file_info_t fileninfo;  // 定义一个cp_file_info_t结构体

    // 解析路径并且保存到 cp_file_info_t结构体中;
    ret = cmd_cp_parse_path(pcmd, &fileninfo);
    if (ret == -1)
        return -1;

    ret = cmd_cp_parse_type(&fileninfo); // 解析文件类型
    if (ret == -1)
        return -1;
    
    return 0;
}

2.3. cp 命令设计与实现 (三)

  • 关于 cp 命令具体情形分析如下:
    • 复制⽂件到⽬标⽬录中
    • 复制⽬录到⽬标⽬录中
      • 如果是第⼀种情形, 源⽂件是普通⽂件时,则直接复制
      • 如果是第⼆种情形, 源⽂件是⽬录时,则需要复制⽬录中所有的⽂件以及⼦⽬录的内容

在获取⽂件类型之后,要分析具体情形,这⾥需要设计⼀个分发函数 cmd_cp_dispatch, 具体

实现如下:

  • 主函数

int cmd_cp_execute(cmd_t *pcmd)
{
    if (NULL == pcmd)
        return -1;
    int ret = 0;
#ifdef DEBUG

#endif
    cp_file_info_t fileninfo; // 定义一个cp_file_info_t结构体

    // 解析路径并且保存到 cp_file_info_t结构体中;
    ret = cmd_cp_parse_path(pcmd, &fileninfo);
    if (ret == -1)
        return -1;

    ret = cmd_cp_parse_type(&fileninfo); // 解析文件类型
    if (ret == -1)
        return -1;
    ret = cmd_cp_dispatch(&fileninfo); // 根据文件类型分发
    if (ret == -1)
        return -1;
    return 0;
}
  • 分发函数
// 处理拷贝操作
int cmd_cp_dispatch(cp_file_info_t *pfileinfo)
{
 if (pfileinfo->src_ftype == FT_FILE)
{
 	return cmd_cp_file(pfileinfo->src_path,pfileinfo->dest_path);
 } 
 else if (pfileinfo->src_ftype == FT_DIR)
{
 return cmd_cp_directory(pfileinfo->src_path,pfileinfo->dest_path);
 } 
}
  • 文件复制直接采⽤ 标准 io ⼆进制读写函数接⼝,这样可以兼容 文件本文件与⼆进制文件:
    • 使用fread /fwirte或者是fgets/fputs两种方式实现cp文件
//	复制文件
int cmd_cp_file(const char *src_path, const char *dest_path)
{
    FILE *src_fp = NULL, *dest_fp = NULL;  // 声明两个文件指针
    size_t rbytes = 0, wbytes = 0;  // 声明两个变量,用于记录读取和写入的字节数
    char buffer[SZ_BUFFER] = {0};  // 声明一个缓冲区,用于读取文件内容
    char buf[SZ_BUFFER] = {0};  // 声明另一个缓冲区,未在代码中使用,可以删除
    int ret1 = 0;  // 声明一个变量,用于记录操作结果

#ifdef DEBUG
    printf(" [ DEBUG ] %s ----> %s\n", src_path, dest_path);  
    // 调试信息,输出源文件路径和目标文件路径
#endif

    if (NULL == src_path || NULL == dest_path)
        return -1;  // 检查输入路径是否为空

    // 打开源文件
    src_fp = fopen(src_path, "r");
    if (NULL == src_fp)
    {
        perror("src fopen():");  // 输出错误信息
        return -1;  // 返回-1表示失败
    }

    // 打开目标文件
    dest_fp = fopen(dest_path, "w+");
    if (NULL == dest_fp)
    {
        perror("dest fopen():");  // 输出错误信息
        fclose(src_fp);  // 关闭已打开的源文件
        return -1;  // 返回-1表示失败
    }

    // 读取源文件内容并写入目标文件
    char *ret = NULL;
    while ((ret = fgets(buf, sizeof(buf), src_fp)) != NULL)
    {
        if (fputs(buf, dest_fp) == EOF)
        {
            perror("fputs():");  // 输出错误信息
            ret1 = -1;  // 设置错误标志
            break;  // 跳出循环
        }
    }
/*
    // 读取源文件内容并写入目标文件(使用fread和fwrite)
    for (;;)
    {
        rbytes = fread(buffer, sizeof(char), SZ_BUFFER, src_fp);  // 从源文件中读取内容到缓冲区
        if (rbytes != 0)    
        {
            wbytes = fwrite(buffer, sizeof(char), rbytes, dest_fp);  // 将缓冲区内容写入目标文件
            if (wbytes != rbytes)
            {
                perror("fwrite():");  // 输出错误信息
                ret1 = -1;  // 设置错误标志
                break;  // 跳出循环
            }
        }
        else
            break;  // 如果没有读取到内容,跳出循环
    }
*/
    // 关闭文件
    fclose(src_fp);  // 关闭源文件
    fclose(dest_fp);  // 关闭目标文件

    // 输出结果
    if (ret1 == -1)
    {
        printf("文件拷贝失败\n");  // 输出失败信息
        return -1;  // 返回-1表示失败
    }
    else
    {
        printf("文件拷贝成功\n");  // 输出成功信息
        return 0;  // 返回0表示成功
    }
}
2.3.1. 如果目标为一个目录+文件

在拷贝cmd_cp_file();函数中先判断一下。使用strrchr函数,查找目标路径是否存在有/,如果存在/就表示有目录,就调用create_directories(dest_dir)函数去创建它,在create_directories函数中会去判断这个目录是否存在,如果存在就不会创建,会直接进入fopen环节,如果不存在,就会使用mkdir函数创建目标目录,之后在进入fopen函数就行操作。

  • mkdir 创建目录
  • 函数头⽂件

#include <sys/stat.h>

#include <sys/types.h>

  • 函数原型

int mkdir(const char *pathname, mode_t mode);

  • 函数功能

在指定路径下创建⼀个⽬录

  • 函数参数

pathname : 路径名

mode : 模式 【0777】

  • 函数返回值

成功 : 返回 0

失败 : 返回 -1


int cmd_cp_file(const char *src_path, const char *dest_path)
{
    // 确保目标路径的父目录存在
    char dest_dir[SZ_FILENAME] = {0};
    strncpy(dest_dir, dest_path, sizeof(dest_dir));
    char *last_slash = strrchr(dest_dir, '/');
    if (last_slash)
    {
        *last_slash = '\0'; // 截取目标路径的父目录
        if (create_directories(dest_dir) != 0) // 创建父目录
        {
            fprintf(stderr, "无法创建目标目录:%s\n", dest_dir);
            return -1;
        }
    }
   
    FILE *src_fp = NULL, *dest_fp = NULL;
    size_t rbytes = 0, wbytes = 0;
    cp_file_info_t pcpinfo;
    char buffer[SZ_BUFFER] = {0};
    char buf[SZ_BUFFER] = {0};
    int ret1 = 0;

#ifdef DEBUG
    printf(" [ DEBUG ] %s ----> %s\n",src_path, dest_path);
#endif
    if (NULL == src_path || NULL ==dest_path)
        return -1;

    // 打开源文件
    src_fp = fopen(src_path, "r");
    if (NULL == src_fp)
    {
        perror("src fopen():");
        return -1;
    }

    // 打开目标文件
    dest_fp = fopen(dest_path, "w+");
    if (NULL == dest_fp)
    {
        perror("dest fopen():");
        fclose(src_fp); // 关闭已打开的源文件
      

        return -1;
    }

    // 读取源文件内容并写入目标文件

    char *ret = NULL;
    while ((ret = fgets(buf, sizeof(buf), src_fp)) != NULL)
    {
        if (fputs(buf, dest_fp) == EOF)
        {
            perror("fputs():");
            ret1 = -1;
            break;
        }
    } 
    // 关闭文件
    fclose(src_fp);
    fclose(dest_fp);

    // 输出结果
    if (ret1 == -1)
    {
        printf("文件拷贝失败\n");
        return -1;
    }
    else
    {
        printf("文件拷贝成功\n");
        return 0;
    }
}

  • 创建目录函数
int create_directories(const char *dest_path)
{
    char *dir = strdup(dest_path); // 复制目标路径字符串
    /*假设 dest_path 是一个指向字符串的指针,例如:

        const char *dest_path = "/home/lxl/imooc/two";
        调用 strdup:

        char *dir = strdup(dest_path);
        此时,dir 将指向一块新分配的内存,
        这块内存中存储了字符串 "/home/lxl/imooc/two" 的副本。

        独立副本:strdup 创建了一个独立的字符串副本,
        这意味着你可以自由修改 dir 指向的字符串,而不会影响原始的 dest_path。
        动态内存分配:strdup 使用动态内存分配(malloc)来存储副本,
        因此你需要在使用完毕后调用 free 来释放这块内存。*/

    if (dir == NULL)
    {
        perror("strdup");
        return -1;
    }

    // 递归创建多级目录
    char *last_slash = dir;

    while ((last_slash = strchr(last_slash + 1, '/')) != NULL)
    { /*
          递归创建多级目录:char *dir = strdup("/home/lxl/imooc/two");
          第一次循环:
              last_slash 指向 /home 中的 /,路径变为 /home。
              创建 /home 目录(如果不存在)。
          第二次循环:
              last_slash 指向 /home/lxl 中的 /,路径变为 /home/lxl。
              创建 /home/lxl 目录(如果不存在)。
          第三次循环:
              last_slash 指向 /home/lxl/imooc 中的 /,路径变为 /home/lxl/imooc。
              创建 /home/lxl/imooc 目录(如果不存在)。
          创建最终目录:
              创建 /home/lxl/imooc/two 目录(如果不存在)。
          */

        *last_slash = '\0'; // 去掉路径中的最后一个组件
        if (mkdir(dir, 0777) == -1)
        {
            /*如果目录成功创建,mkdir 返回 0。
        如果目录已经存在,mkdir 返回 -1,并将 errno 设置为 EEXIST。
        如果发生其他错误,mkdir 返回 -1,并将 errno 设置为其他值。*/
            if (errno != EEXIST)
            //  errno 不等于 EEXIST,说明错误类型不是 "File exists",而是其他类型的错误。
            //  errno 等于 EEXIST,说明错误类型是 "File exists"。 [文件存在]
            {
                perror("mkdir1:");
                free(dir);
                return -1;
            }
        }
        *last_slash = '/'; // 恢复路径中的最后一个组件
    }

    // 创建最终目录
    if (mkdir(dir, 0777) == -1)
    {
        if (errno != EEXIST)
        {
            perror("mkdir2:");
            free(dir);
            return -1;
        }
    }

    free(dir);
    return 0;
}

2.4. cp 命令设计与实现 -(四)

  • ⽬录复制到⽬录的基本思路如下:
    • 遍历⽬录
    • 判断是⽂件还是⽬录
      • ⽂件 则直接进⾏复制
      • ⽬录 则进⾏递归
  • opendir 打开目录
  • 函数头⽂件

#include <sys/types.h>

#include <dirent.h>

  • 函数原型

DIR *opendir(const char *name);

  • 函数功能

打开⼀个⽬录

  • 函数参数

name : ⽬录路径名

  • 函数返回值

成功 : 返回⽬录流的指针

失败 : 返回 NULL, 并设置错误编号到 errno

  • readdir 读取目录内容
  • 函数头⽂件

#include <dirent.h>

  • 函数原型

struct dirent *readdir(DIR *dirp);

  • 函数功能

读取⽬录中的⼀项,并将信息保存到 struct dirent 指针中, ⼀般⽤于遍历⽬录

  • 函数参数

dirp : ⽬录流指针 【目录描述符指针】

  • 函数返回值

成功 : 返回⽬录项信息结构体指针

失败 : 返回 NULL, 并设置错误编号到 errno

使用readdir()函数遍历目录中的子目录,并且输出目录名字,【是当前目录中的目录、文件名字,不包括子目录下面的文件名】

struct dirent {
    ino_t          d_ino;       // inode 号码(文件或目录的唯一标识符)
    off_t          d_off;       // 当前目录项在目录流中的位置
    unsigned short d_reclen;    // 该目录项的长度
    unsigned char  d_type;      // 目录项的类型(文件、目录、链接等)
    char           d_name[256]; // 目录项的名称(文件或目录名)
};
  • demo
#include <stdio.h>  // 包含标准输入输出库
#include <stdlib.h>  // 包含标准库,用于退出程序等
#include <string.h>  // 包含字符串处理函数
#include <unistd.h>  // 包含 POSIX 操作系统 API
#include <sys/types.h>  // 包含系统数据类型
#include <sys/stat.h>  // 包含文件状态处理函数
#include <dirent.h>  // 包含目录操作函数

int main(int argc, char *argv[])
{
    if(argc != 2)  // 检查命令行参数个数是否为2
    {
        printf("Usage: %s <dir>\n", argv[0]);  // 输出使用说明
        exit(1);  // 退出程序,返回值为1表示错误
    }

    DIR *dir = NULL;  // 声明一个目录流指针
    struct dirent *pdirent = NULL;  // 声明一个目录项指针,dirent 结构体是系统定义的结构体

    dir = opendir(argv[1]);  // 打开目录,返回目录流
    if(dir == NULL)  // 检查目录是否成功打开
    {
        perror("opendir");  // 输出错误信息
        exit(1);  // 退出程序,返回值为1表示错误
    }

    while((pdirent = readdir(dir)) != NULL)  // 读取目录项,返回目录项指针
    {       // readdir() 读取目录项,一次遍历一项
        if(strcmp(pdirent->d_name, ".") == 0 || strcmp(pdirent->d_name, "..") == 0)
            continue;  // 跳过当前目录(.)和父目录(..)

        printf("%s\n", pdirent->d_name);  // 输出目录项名称
    }

    closedir(dir);  // 关闭目录流
    return 0;  // 程序正常退出,返回值为0表示成功
}

运行结果:

demo



int cmd_cp_dir(const char *src_path, const char *dest_path)
{
    DIR *dir = NULL;               // 声明一个目录流指针
    struct dirent *pdirent = NULL; // 声明一个目录项指针,dirent 结构体是系统定义的结构体
    cp_file_info_t info ; // 声明一个 cp_file_info_t 结构体变量,
    create_directories(dest_path); // 递归创建目标多级目录

    dir = opendir(src_path); // 打开源文件目录,返回目录流
    if (dir == NULL)         // 检查目录是否成功打开
    {
        perror("opendir"); // 输出错误信息
        exit(-1);          // 退出程序,返回值为-1表示错误
    }

    while ((pdirent = readdir(dir)) != NULL) // 读取目录项,返回目录项指针
    {                                        // readdir() 读取目录项,一次遍历一项
        if (strcmp(pdirent->d_name, ".") == 0 || strcmp(pdirent->d_name, "..") == 0)
            continue; // 跳过当前目录(.)和父目录(..)
#ifdef DEBUG
        printf("[DEBUG]: pdirent->d_name %s\n", pdirent->d_name); // 输出目录项名称

#endif
        make_path(&info,src_path, dest_path,pdirent->d_name ); // 构造源文件路径
#ifdef DEBUG
        printf("[DEBUG] info src_path  :%s\n", info.src_path); // 输出源文件路径
        printf("[DEBUG] info dest_path :%s\n", info.dest_path); // 输出目标文件路径
#endif
        //获取源目录文件类型
        info.src_ftype = get_file_type(info.src_path); // 获取源文件类型
        if (info.src_ftype == FT_DIR) // 如果是目录
        {
            cmd_cp_dir(info.src_path, info.dest_path);
            // 递归调用 cmd_cp_dir() 函数

        }
        else if (info.src_ftype == FT_FILE) // 如果是文件
        {
            cmd_cp_file(info.src_path, info.dest_path);
            // 调用 cmd_cp_file() 函数
        }
    }

    closedir(dir); // 关闭目录流

    return 0;
}

// cp test test1  test/1.txt test/2.txt  test1/1.txt test1/2.txt
void make_path(cp_file_info_t *pinfo,
     const char *src_path,
     const char *dest_path,
     const char *filename)
{
memset(pinfo->src_path, 0, sizeof(pinfo->src_path));
memset(pinfo->dest_path, 0, sizeof(pinfo->dest_path));

strcpy(pinfo->src_path, src_path);
strcat(pinfo->src_path, "/");  // 拼接路径
strcat(pinfo->src_path, filename); // 拼接路径

strcpy(pinfo->dest_path, dest_path);
strcat(pinfo->dest_path, "/");
strcat(pinfo->dest_path, filename);

}
2.4.1. 完整cp代码:
#include "cmd_cp.h"

#define DEBUG

int cmd_cp_execute(cmd_t *pcmd)
{
    if (NULL == pcmd)
        return -1;
    int ret = 0;
#ifdef DEBUG

#endif
    cp_file_info_t fileninfo; // 定义一个cp_file_info_t结构体

    // 解析路径并且保存到 cp_file_info_t结构体中;
    ret = cmd_cp_parse_path(pcmd, &fileninfo);
    if (ret == -1)
        return -1;

    ret = cmd_cp_parse_type(&fileninfo); // 解析文件类型
    if (ret == -1)
        return -1;
    ret = cmd_cp_dispatch(&fileninfo); // 根据文件类型分发
    if (ret == -1)
        return -1;
    return 0;
}

int cmd_cp_parse_path(cmd_t *pcmd, cp_file_info_t *pcpinfo)
{
    if (NULL == pcmd || NULL == pcpinfo)
        return -1;

    strcpy(pcpinfo->src_path, pcmd->cmd_arg_list[0]);
    strcpy(pcpinfo->dest_path, pcmd->cmd_arg_list[1]);

#ifdef DEBUG
    printf("[DEBUG] 源路径是 :%s\n", pcpinfo->src_path);
    printf("[DEBUG] 目标路径是:%s\n", pcpinfo->dest_path);
#endif
    return 0;
}

file_type_t get_file_type(const char *path)
{
    int ret = 0;
    struct stat statbuf;
    ret = stat(path, &statbuf);
    if (ret == -1)
    {
        perror("stat():");
        return FT_ERROR;
    }
    if (S_ISDIR(statbuf.st_mode))
        return FT_DIR;

    if (S_ISREG(statbuf.st_mode))
        return FT_FILE;

    return FT_UNKNOWN;
}

int cmd_cp_parse_type(cp_file_info_t *pcpinfo)
{
    enum file_type ftpye;
    ftpye = get_file_type(pcpinfo->src_path);
    if (ftpye == FT_ERROR || ftpye == FT_UNKNOWN)
    {
        perror("get_file_type():");
        return -1;
    }
    else
        pcpinfo->src_ftype = ftpye;
#ifdef DEBUG
    if (pcpinfo->src_ftype == FT_DIR)
        printf("[DEBUG]src is dir\n"); // 目录

    else if (pcpinfo->src_ftype == FT_FILE)
        printf("[DEBUG]src is file\n"); // 文件
#endif
    return 0;
}

// 处理拷贝操作
int cmd_cp_dispatch(cp_file_info_t *pcpinfo)
{
    if (pcpinfo->src_ftype == FT_FILE)
    {
        printf("cp is file\n");

        return cmd_cp_file(pcpinfo->src_path, pcpinfo->dest_path);
    }
    else if (pcpinfo->src_ftype == FT_DIR)
    {
        printf("cp is dir\n");
         return cmd_cp_dir(pcpinfo->src_path, pcpinfo->dest_path); // 如果需要支持目录复制,加入此功能
    }
    return -1;
}

int cmd_cp_file(const char *src_path, const char *dest_path)
{
    // 确保目标路径的父目录存在
    char dest_dir[SZ_FILENAME] = {0};
    strncpy(dest_dir, dest_path, sizeof(dest_dir));
    char *last_slash = strrchr(dest_dir, '/');
    if (last_slash)
    {
        *last_slash = '\0'; // 截取目标路径的父目录
        if (create_directories(dest_dir) != 0) // 创建父目录
        {
            fprintf(stderr, "无法创建目标目录:%s\n", dest_dir);
            return -1;
        }
    }
   
    FILE *src_fp = NULL, *dest_fp = NULL;
    size_t rbytes = 0, wbytes = 0;
    cp_file_info_t pcpinfo;
    char buffer[SZ_BUFFER] = {0};
    char buf[SZ_BUFFER] = {0};
    int ret1 = 0;

#ifdef DEBUG
    printf(" [ DEBUG ] %s ----> %s\n",src_path, dest_path);
#endif
    if (NULL == src_path || NULL ==dest_path)
        return -1;

    // 打开源文件
    src_fp = fopen(src_path, "r");
    if (NULL == src_fp)
    {
        perror("src fopen():");
        return -1;
    }

    // 打开目标文件
    dest_fp = fopen(dest_path, "w+");
    if (NULL == dest_fp)
    {
        perror("dest fopen():");
        fclose(src_fp); // 关闭已打开的源文件
      

        return -1;
    }

    // 读取源文件内容并写入目标文件

    char *ret = NULL;
    while ((ret = fgets(buf, sizeof(buf), src_fp)) != NULL)
    {
        if (fputs(buf, dest_fp) == EOF)
        {
            perror("fputs():");
            ret1 = -1;
            break;
        }
    } 
    // 关闭文件
    fclose(src_fp);
    fclose(dest_fp);

    // 输出结果
    if (ret1 == -1)
    {
        printf("文件拷贝失败\n");
        return -1;
    }
    else
    {
        printf("文件拷贝成功\n");
        return 0;
    }
}

int cmd_cp_dir(const char *src_path, const char *dest_path)
{
    DIR *dir = NULL;               // 声明一个目录流指针
    struct dirent *pdirent = NULL; // 声明一个目录项指针,dirent 结构体是系统定义的结构体
    cp_file_info_t info ; // 声明一个 cp_file_info_t 结构体变量,
    create_directories(dest_path); // 递归创建目标多级目录

    dir = opendir(src_path); // 打开目录,返回目录流
    if (dir == NULL)         // 检查目录是否成功打开
    {
        perror("opendir"); // 输出错误信息
        exit(-1);          // 退出程序,返回值为-1表示错误
    }

    while ((pdirent = readdir(dir)) != NULL) // 读取目录项,返回目录项指针
    {                                        // readdir() 读取目录项,一次遍历一项
        if (strcmp(pdirent->d_name, ".") == 0 || strcmp(pdirent->d_name, "..") == 0)
            continue; // 跳过当前目录(.)和父目录(..)
#ifdef DEBUG
        printf("[DEBUG]: pdirent->d_name %s\n", pdirent->d_name); // 输出目录项名称

#endif
        make_path(&info,src_path, dest_path,pdirent->d_name ); // 构造源文件路径
#ifdef DEBUG
        printf("[DEBUG] info src_path  :%s\n", info.src_path); // 输出源文件路径
        printf("[DEBUG] info dest_path :%s\n", info.dest_path); // 输出目标文件路径
#endif
        //获取源目录文件类型
        info.src_ftype = get_file_type(info.src_path); // 获取源文件类型
        if (info.src_ftype == FT_DIR) // 如果是目录
        {
            cmd_cp_dir(info.src_path, info.dest_path);
            // 递归调用 cmd_cp_dir() 函数

        }
        else if (info.src_ftype == FT_FILE) // 如果是文件
        {
            cmd_cp_file(info.src_path, info.dest_path);
            // 调用 cmd_cp_file() 函数
        }
    }

    closedir(dir); // 关闭目录流

    return 0;
}


int create_directories(const char *dest_path)
{
    char *dir = strdup(dest_path); // 复制目标路径字符串
    /*假设 dest_path 是一个指向字符串的指针,例如:

        const char *dest_path = "/home/lxl/imooc/two";
        调用 strdup:

        char *dir = strdup(dest_path);
        此时,dir 将指向一块新分配的内存,
        这块内存中存储了字符串 "/home/lxl/imooc/two" 的副本。

        独立副本:strdup 创建了一个独立的字符串副本,
        这意味着你可以自由修改 dir 指向的字符串,而不会影响原始的 dest_path。
        动态内存分配:strdup 使用动态内存分配(malloc)来存储副本,
        因此你需要在使用完毕后调用 free 来释放这块内存。*/

    if (dir == NULL)
    {
        perror("strdup");
        return -1;
    }

    // 递归创建多级目录
    char *last_slash = dir;

    while ((last_slash = strchr(last_slash + 1, '/')) != NULL)
    { /*
          递归创建多级目录:char *dir = strdup("/home/lxl/imooc/two");
          第一次循环:
              last_slash 指向 /home 中的 /,路径变为 /home。
              创建 /home 目录(如果不存在)。
          第二次循环:
              last_slash 指向 /home/lxl 中的 /,路径变为 /home/lxl。
              创建 /home/lxl 目录(如果不存在)。
          第三次循环:
              last_slash 指向 /home/lxl/imooc 中的 /,路径变为 /home/lxl/imooc。
              创建 /home/lxl/imooc 目录(如果不存在)。
          创建最终目录:
              创建 /home/lxl/imooc/two 目录(如果不存在)。
          */

        *last_slash = '\0'; // 去掉路径中的最后一个组件
        if (mkdir(dir, 0777) == -1)
        {
            /*如果目录成功创建,mkdir 返回 0。
        如果目录已经存在,mkdir 返回 -1,并将 errno 设置为 EEXIST。
        如果发生其他错误,mkdir 返回 -1,并将 errno 设置为其他值。*/
            if (errno != EEXIST)
            //  errno 不等于 EEXIST,说明错误类型不是 "File exists",而是其他类型的错误。
            //  errno 等于 EEXIST,说明错误类型是 "File exists"。 [文件存在]
            {
                perror("mkdir1:");
                free(dir);
                return -1;
            }
        }
        *last_slash = '/'; // 恢复路径中的最后一个组件
    }

    // 创建最终目录
    if (mkdir(dir, 0777) == -1)
    {
        if (errno != EEXIST)
        {
            perror("mkdir2:");
            free(dir);
            return -1;
        }
    }

    free(dir);
    return 0;
}

// cp test test1  test/1.txt test/2.txt  test1/1.txt test1/2.txt
void make_path(cp_file_info_t *pinfo,
     const char *src_path,
     const char *dest_path,
     const char *filename)
{
    memset(pinfo->src_path, 0, sizeof(pinfo->src_path));
    memset(pinfo->dest_path, 0, sizeof(pinfo->dest_path));
    
    strcpy(pinfo->src_path, src_path);
    strcat(pinfo->src_path, "/");  // 拼接路径
    strcat(pinfo->src_path, filename); // 拼接路径
    
    strcpy(pinfo->dest_path, dest_path);
    strcat(pinfo->dest_path, "/");
    strcat(pinfo->dest_path, filename);

}
2.4.2. 对整个cp代码的整体分析

这段代码实现了一个简单的文件和目录复制功能,类似于Linux中的cp命令。代码分为多个函数,每个函数都有特定的功能。以下是逐行解释:

1. cmd_cp_execute 函数

这是主函数,负责执行复制操作。它调用其他辅助函数来完成路径解析、文件类型解析和实际的复制操作。
int cmd_cp_execute(cmd_t *pcmd)
{
     if (NULL == pcmd)	return -1; // 如果传入的命令结构体为空,返回错误
     int ret = 0;
#ifdef DEBUG
    // 调试信息(如果定义了DEBUG宏)
#endif
    cp_file_info_t fileninfo; // 定义一个cp_file_info_t结构体
// 解析路径并且保存到 cp_file_info_t结构体中;
    ret = cmd_cp_parse_path(pcmd, &fileninfo); // 解析源路径和目标路径
    if (ret == -1)
        return -1; // 如果解析失败,返回错误
    
    ret = cmd_cp_parse_type(&fileninfo); // 解析文件类型
    if (ret == -1) 	return -1; // 如果解析失败,返回错误
    
    ret = cmd_cp_dispatch(&fileninfo); // 根据文件类型分发
    if (ret == -1)	return -1; // 如果分发失败,返回错误
return 0; // 成功返回0

2.cmd_cp_parse_path 函数:

使用了 strcpy函数 将路径,拷贝到CP函数中对应的结构体中

 
这个函数负责解析命令行参数中的源路径和目标路径,并将它们保存到cp_file_info_t结构体中。
int cmd_cp_parse_path(cmd_t *pcmd, cp_file_info_t *pcpinfo)
{
    if (NULL == pcmd || NULL == pcpinfo) return -1; // 如果传入的指针为空,返回错误
    strcpy(pcpinfo->src_path, pcmd->cmd_arg_list[0]); // 复制源路径
    strcpy(pcpinfo->dest_path, pcmd->cmd_arg_list[1]); // 复制目标路径
#ifdef DEBUG
    printf("[DEBUG] 源路径是 :%s\n", pcpinfo->src_path); // 打印源路径
    printf("[DEBUG] 目标路径是:%s\n", pcpinfo->dest_path); // 打印目标路径
#endif
    return 0; // 成功返回0
}

3. get_file_type 函数:

使用stat函数 获取源文件的各种信息,[文件类型,大小,用户id,用户组id等等]


这个函数通过stat系统调用获取文件的类型(文件、目录或其他类型)。

file_type_t get_file_type(const char *path)
{
    int ret = 0;
    struct stat statbuf; // 定义一个stat结构体
    ret = stat(path, &statbuf); // 获取文件状态
    if (ret == -1)
    {
        perror("stat():"); // 如果失败,打印错误信息
        return FT_ERROR; // 返回错误类型
    }
    if (S_ISDIR(statbuf.st_mode)) // 如果是目录
        return FT_DIR; // 返回目录类型
    if (S_ISREG(statbuf.st_mode)) // 如果是普通文件
        return FT_FILE; // 返回文件类型
    return FT_UNKNOWN; // 其他类型
}

4.cmd_cp_parse_type 函数

这个函数模块主要是,调度get_file_type()函数,去获取文件类型,获取结果之后,保存到cp_file_info_t结构体中

这个函数调用get_file_type函数来获取源路径的文件类型,并将其保存到cp_file_info_t结构体中。
int cmd_cp_parse_type(cp_file_info_t *pcpinfo)
{
    enum file_type ftpye;
    ftpye = get_file_type(pcpinfo->src_path); // 获取源路径的文件类型
    if (ftpye == FT_ERROR || ftpye == FT_UNKNOWN)
    {
        perror("get_file_type():"); // 如果类型未知或出错,打印错误信息
        return -1; // 返回错误
    }
    else
        pcpinfo->src_ftype = ftpye; // 保存文件类型
#ifdef DEBUG
    if (pcpinfo->src_ftype == FT_DIR)
        printf("[DEBUG]src is dir\n"); // 如果是目录,打印调试信息
    else if (pcpinfo->src_ftype == FT_FILE)
        printf("[DEBUG]src is file\n"); // 如果是文件,打印调试信息
#endif
    return 0; // 成功返回0
}

5. cmd_cp_dispatch 函数

这个函数是将保存到cp_file_info_t结构体中的文件类型调取出来,

按照如果是文件调用文件函数去操作,

如果是目录,调用目录函数去操作

这个函数根据文件类型调用不同的复制函数(文件或目录)。
int cmd_cp_dispatch(cp_file_info_t *pcpinfo)
{
    if (pcpinfo->src_ftype == FT_FILE) // 如果是文件
    {
        printf("cp is file\n");
        return cmd_cp_file(pcpinfo->src_path, pcpinfo->dest_path); // 调用文件复制函数
    }
    else if (pcpinfo->src_ftype == FT_DIR) // 如果是目录
    {
        printf("cp is dir\n");
        return cmd_cp_dir(pcpinfo->src_path, pcpinfo->dest_path); // 调用目录复制函数
    }
    return -1; // 如果类型未知,返回错误
}

6. cmd_cp_file 函数

这个函数负责复制文件。它打开源文件和目标文件,逐行读取源文件内容并写入目标文件。
int cmd_cp_file(const char *src_path, const char *dest_path)
{
    // 确保目标路径的父目录存在
    char dest_dir[SZ_FILENAME] = {0};
    strncpy(dest_dir, dest_path, sizeof(dest_dir)); // 复制目标路径
    char *last_slash = strrchr(dest_dir, '/'); // 找到最后一个斜杠
    if (last_slash)
    {
        *last_slash = '\0'; // 截取目标路径的父目录
        if (create_directories(dest_dir) != 0) // 创建父目录
        {
            fprintf(stderr, "无法创建目标目录:%s\n", dest_dir); // 如果失败,打印错误信息
            return -1; // 返回错误
        }
    }
    FILE *src_fp = NULL, *dest_fp = NULL; // 定义文件指针
    size_t rbytes = 0, wbytes = 0; // 定义读取和写入的字节数
    char buffer[SZ_BUFFER] = {0}; // 定义缓冲区
    char buf[SZ_BUFFER] = {0}; // 定义缓冲区
    int ret1 = 0; // 定义返回值
#ifdef DEBUG
    printf(" [ DEBUG ] %s ----> %s\n",src_path, dest_path); // 打印调试信息
#endif
    if (NULL == src_path || NULL ==dest_path)
        return -1; // 如果路径为空,返回错误
// 打开源文件
    src_fp = fopen(src_path, "r");
    if (NULL == src_fp)
    {
        perror("src fopen():"); // 如果失败,打印错误信息
        return -1; // 返回错误
    }
    
    // 打开目标文件
    dest_fp = fopen(dest_path, "w+");
    if (NULL == dest_fp)
    {
        perror("dest fopen():"); // 如果失败,打印错误信息
        fclose(src_fp); // 关闭已打开的源文件
        return -1; // 返回错误
    }
    
    // 读取源文件内容并写入目标文件
    char *ret = NULL;
    while ((ret = fgets(buf, sizeof(buf), src_fp)) != NULL) // 逐行读取
    {
        if (fputs(buf, dest_fp) == EOF) // 写入目标文件
        {
            perror("fputs():"); // 如果失败,打印错误信息
            ret1 = -1; // 设置返回值为错误
            break; // 跳出循环
        }
    } 
    // 关闭文件
    fclose(src_fp);
    fclose(dest_fp);
    
    // 输出结果
    if (ret1 == -1)
    {
        printf("文件拷贝失败\n"); // 如果失败,打印失败信息
        return -1; // 返回错误
    }
    else
        printf("文件拷贝成功\n"); // 如果成功,打印成功信息
        return 0; // 成功返回0
    
}

7. cmd_cp_dir 函数

这个函数负责递归复制目录。它打开源目录,遍历目录中的每个文件或子目录,并调用相应的复制函数。

int cmd_cp_dir(const char *src_path, const char *dest_path)
{
    DIR *dir = NULL; // 声明一个目录流指针
    struct dirent *pdirent = NULL; // 声明一个目录项指针
    cp_file_info_t info; // 声明一个cp_file_info_t结构体变量
    create_directories(dest_path); // 递归创建目标多级目录
dir = opendir(src_path); // 打开目录
if (dir == NULL) // 如果打开失败
{
    perror("opendir"); // 打印错误信息
    exit(-1); // 退出程序
}

while ((pdirent = readdir(dir)) != NULL) // 遍历目录
{
    if (strcmp(pdirent->d_name, ".") == 0 || strcmp(pdirent->d_name, "..") == 0)
        continue; // 跳过当前目录(.)和父目录(..)
#ifdef DEBUG
        printf("[DEBUG]: pdirent->d_name %s\n", pdirent->d_name); // 打印目录项名称
#endif
        make_path(&info, src_path, dest_path, pdirent->d_name); // 构造源文件路径和目标文件路径
#ifdef DEBUG
        printf("[DEBUG] info src_path  :%s\n", info.src_path); // 打印源路径
        printf("[DEBUG] info dest_path :%s\n", info.dest_path); // 打印目标路径
#endif
        // 获取源目录文件类型
        info.src_ftype = get_file_type(info.src_path); // 获取文件类型
        if (info.src_ftype == FT_DIR) // 如果是目录
        {
            cmd_cp_dir(info.src_path, info.dest_path); // 递归调用目录复制函数
        }
        else if (info.src_ftype == FT_FILE) // 如果是文件
        {
            cmd_cp_file(info.src_path, info.dest_path); // 调用文件复制函数
        }
    }
closedir(dir); // 关闭目录流
return 0; // 成功返回0
}

8. create_directories 函数

这个函数递归创建多级目录。它使用mkdir系统调用创建目录。
int create_directories(const char *dest_path)
{
    char *dir = strdup(dest_path); // 复制目标路径字符串
    if (dir == NULL)
    {
        perror("strdup"); // 如果复制失败,打印错误信息
        return -1; // 返回错误
    }
// 递归创建多级目录
char *last_slash = dir;
while ((last_slash = strchr(last_slash + 1, '/')) != NULL) // 遍历路径中的每个斜杠
{
    *last_slash = '\0'; // 去掉路径中的最后一个组件
    if (mkdir(dir, 0777) == -1) // 创建目录
    {
        if (errno != EEXIST) // 如果目录已经存在,忽略错误
        {
            perror("mkdir1:"); // 如果其他错误,打印错误信息
            free(dir); // 释放内存
            return -1; // 返回错误
        }
    }
    *last_slash = '/'; // 恢复路径中的最后一个组件
}

// 创建最终目录
if (mkdir(dir, 0777) == -1) // 创建最终目录
{
    if (errno != EEXIST) // 如果目录已经存在,忽略错误
    {
        perror("mkdir2:"); // 如果其他错误,打印错误信息
        free(dir); // 释放内存
        return -1; // 返回错误
    }
}

free(dir); // 释放内存
return 0; // 成功返回0
}

9. make_path 函数

这个函数用于构造源路径和目标路径。它将文件名拼接到路径中。
void make_path(cp_file_info_t *pinfo,
     const char *src_path,
     const char *dest_path,
     const char *filename)
{
    memset(pinfo->src_path, 0, sizeof(pinfo->src_path)); // 清空源路径
    memset(pinfo->dest_path, 0, sizeof(pinfo->dest_path)); // 清空目标路径
    strcpy(pinfo->src_path, src_path); // 复制源路径
    strcat(pinfo->src_path, "/");  // 拼接路径
    strcat(pinfo->src_path, filename); // 拼接文件名
    
    strcpy(pinfo->dest_path, dest_path); // 复制目标路径
    strcat(pinfo->dest_path, "/"); // 拼接路径
    strcat(pinfo->dest_path, filename); // 拼接文件名
}

这段代码实现了一个简单的文件和目录复制功能。它通过解析路径、判断文件类型,并根据类型调用相应的复制函数来完成任务。代码中使用了多个辅助函数来分解任务,使代码更加清晰和模块化。

3. ls 命令设计与实现 (⼀)

  • 编程实现 ls -l 命令功能,具体要求如下:
    • 显示⽂件类型
    • 显示⽂件权限
    • 显示⽂件所属⽤户名
    • 显示⽂件所属⽤户组名
    • 显示⽂件最后⼀次修改时间
    • 显示⽂件名

    • step 1 : 遍历⽬录, 并获取⽬录下的⽂件名
    • step 2 : 通过系统 stat 函数来获取每个⽂件的属性
    • step 3 : 解析⽂件属性信息,进⾏显示

目的:保存获取的⽂件属性信息,并进⾏输出

  • 基本框架 : 在系统的 struct stat 结构体上进⾏扩展, 产⽣新的结构体, 具体设计如下:

#ifndef __CMD_LS_H__
#define __CMD_LS_H__

#include <stdio.h>     // 包含标准输入输出库
#include <stdlib.h>    // 包含标准库,用于退出程序等
#include <string.h>    // 包含字符串处理函数
#include <unistd.h>    // 包含 POSIX 操作系统 API
#include <sys/types.h> // 包含系统数据类型
#include <sys/stat.h>  // 包含文件状态处理函数
#include <dirent.h>    // 包含目录操作函数
#include <errno.h>

#include "cmd_handle.h"

#define SZ_LS_NAME 64
#define SZ_LS_PERMISSION 10
#define SZ_LS_TIME 32
#define SZ_LS_LINK_CONTENT 64

#define DEBUG

struct file_attribute
{
    struct stat f_attr_stat_info; // 保留stat的原本文件属性信息

    char f_attr_type;                             // 文件类型
    char f_attr_uname[SZ_LS_NAME];                // 文件所有者
    char f_attr_gname[SZ_LS_NAME];                // 文件所属组
    char f_attr_mtime[SZ_LS_TIME];                // 文件修改时间
    char f_attr_permission[SZ_LS_PERMISSION];     // 文件权限
    char f_attr_name[SZ_LS_NAME];                 // 文件名
    char f_attr_link_content[SZ_LS_LINK_CONTENT]; // 链接文件内容
};


extern int cmd_ls_execute(cmd_t *pcmd);
#endif // __CMD_LS_H__
  • 当cmd_handle.c模块中解析出来,如果是ls命令就会调用 cmd_ls模块中的cmd_ls_execute函数。去执行相关指令
#include "cmd_ls.h"

// ls -l <path>
int cmd_ls_execute(cmd_t *pcmd)  // pcmd  得到ls命令的内容
{
    if (NULL == pcmd)   return -1;  // 判断自定义结构体是否为空
    if (pcmd->cmd_arg_count != 2)   // 判断命令参数个数是否为2
    {
        fprintf(stderr, "Command argument Error.\n");
        return -1;
    }

    if (pcmd->cmd_arg_list[1] != NULL)   // 判断命令参数1是否为空
        return cmd_list_directory(pcmd->cmd_arg_list[1]); // 调用遍历目录函数
    else
        return -1;
    int ret = 0;
#ifdef DEBUG
    printf("cmd_ls_execute\n");
#endif

    return 0;
}

int cmd_list_directory(const char *dirpath)  // 遍历目录
{
    DIR *pdir = NULL;              // 目录指针
    struct dirent *pdirent = NULL; // 目录项指针
    file_attribute_t f_attr;   // 自定义文件属性结构体
    
    char path[128] = {0};

    pdir = opendir(dirpath); // 打开目录
    if (pdir == NULL)
    {
        perror("open(): ");
        return -1;
    }
    while ((pdirent = readdir(pdir)) != NULL) // 读取目录项,返回目录项指针
    {                                         // readdir() 读取目录项,一次遍历一项
        if (strcmp(pdirent->d_name, ".") == 0 || strcmp(pdirent->d_name, "..") == 0)
            continue; // 跳过当前目录(.)和父目录(..)continue;
#ifdef DEBUG
        printf("[DEBUG]: pdirent->d_name %s\n", pdirent->d_name); // 输出目录项名称
        
#endif
    }
    closedir(pdir); // 关闭目录
    return 0;
}

3.1. ls 命令设计与实现 (⼆)获取文件类型

  • 基本思路:
    • 通过 stat 函数获取⽂件信息,并保存到 struct stat 结构体中
    • struct stat 结构体 st_mode 成员则可以解析到⽂件类型

注意点:

对于链接⽂件本身需要通过 lstat 函数来获取,否则获取的是链接⽂件所指向的内容

获取属性的主要接⼝为 get_file_attr , 并调⽤

#include "cmd_ls.h"

// ls -l <path>
int cmd_ls_execute(cmd_t *pcmd)  // pcmd  得到ls命令的内容
{
    if (NULL == pcmd)   return -1;  // 判断自定义结构体是否为空
    if (pcmd->cmd_arg_count != 2)   // 判断命令参数个数是否为2
    {
        fprintf(stderr, "Command argument Error.\n");
        return -1;
    }

    if (pcmd->cmd_arg_list[1] != NULL)   // 判断命令参数1是否为空
        return cmd_list_directory(pcmd->cmd_arg_list[1]); // 调用遍历目录函数
    else
        return -1;
    int ret = 0;
#ifdef DEBUG
    printf("cmd_ls_execute\n");
#endif

    return 0;
}

int cmd_list_directory(const char *dirpath)  // 遍历目录
{
    DIR *pdir = NULL;              // 目录指针
    struct dirent *pdirent = NULL; // 目录项指针
    file_attribute_t f_attr;   // 自定义文件属性结构体
    
    char path[128] = {0};

    pdir = opendir(dirpath); // 打开目录
    if (pdir == NULL)
    {
        perror("open(): ");
        return -1;
    }
    while ((pdirent = readdir(pdir)) != NULL) // 读取目录项,返回目录项指针
    {                                         // readdir() 读取目录项,一次遍历一项
        if (strcmp(pdirent->d_name, ".") == 0 || strcmp(pdirent->d_name, "..") == 0)
            continue; // 跳过当前目录(.)和父目录(..)continue;
#ifdef DEBUG
        printf("[DEBUG]: pdirent->d_name %s\n", pdirent->d_name); // 输出目录项名称
        
#endif
        memset(&f_attr, 0, sizeof(f_attr)); // 清空自定义文件属性结构体
        make_path_ls(path , dirpath, pdirent->d_name); // 合成具体文件的路径
        if( pdirent->d_type == DT_LNK)  // 如果是软连接文件
            get_file_attr(&f_attr, path, pdirent->d_name, true); // 获取链接文件属性
        else
            get_file_attr(&f_attr, path, pdirent->d_name, false); // 获取普通文件属性

        show_file_attrbutes(&f_attr); // 打印文件属性
    }
    closedir(pdir); // 关闭目录
    return 0;
}

/*  
    attr 自定义文件属性结构体指针
    path 文件路径
    filename 文件名
    islink 是否是链接文件
*/
// 获取文件属性
int get_file_attr(file_attribute_t *attr,
                  const char *path,
                  const char *filename,
                  bool islink) 
{       
    if (NULL == attr || NULL == path || NULL == filename)   return -1;
    int ret ;
    if (islink) // 如果是链接文件
            // lstat() 获取链接文件属性函数
        ret = lstat(path, &attr->f_attr_stat_info); // 获取链接文件属性保存到自定义文件属性结构体中
        
    else
        ret = stat(path, &attr->f_attr_stat_info); // 获取普通文件属性保存到自定义文件属性结构体中

    if (ret == -1 )
    {
        perror("[DEEPR]stat(): ")   ;
        return -1;
    }  

    
    return 0;
}

void show_file_attrbutes(file_attribute_t *attr)// 打印文件属性
{
    printf("File Name: %c\n", attr->f_attr_type);  // 文件类型
    
}


// ls -l test   test/1.txt  合成具体文件的路径
int make_path_ls(char *path , const char *dirpath, const char *filename)
{
    if (NULL == dirpath || NULL == filename || NULL == path)
        return -1;
    strcpy(path, dirpath);
    strcat(path, "/");
    strcat(path, filename);
    return 0;
}

获取⽂件类型 是通过解析 struct stat 结构体中的 st_mode 的第 [15:12] bit 来判断

通过与 S_IFMT 进⾏按位与操作,则可以得到 [15:12] 的值

在与定义的⼋进制的值进⾏⽐对即可,具体定义如下

#define S_IFMT 00170000 // 文件类型位掩码
#define S_IFSOCK 0140000 // 套接字
#define S_IFLNK 0120000 // 符号链接
#define S_IFREG 0100000 // 普通文件
#define S_IFBLK 0060000 // 块设备
#define S_IFDIR 0040000 // 目录
#define S_IFCHR 0020000 // 字符设备
#define S_IFIFO 0010000     // FIFO

获取⽂件类型的接⼝设计为 get_file_type_ls , 将获取的⽂件属性信息保存到结构体相应的成员

中,在 get_file_attr函数中进⾏调⽤


// 获取文件类型
int get_file_type_ls(file_attribute_t *pfile_attr)
{
    if (NULL == pfile_attr)
        return -1;
    mode_t mode = pfile_attr->f_attr_stat_info.st_mode; // 获取文件类型
    switch (mode & S_IFMT)                              // S_IFMT 是一个宏,用于获取文件类型; 按位与
    {
    case S_IFBLK: // 块设备文件
        pfile_attr->f_attr_type = 'b';
        break;
    case S_IFCHR: // 字符设备文件
        pfile_attr->f_attr_type = 'c';
        break;
    case S_IFDIR: // 目录文件
        pfile_attr->f_attr_type = 'd';
        break;
    case S_IFIFO: // 管道文件
        pfile_attr->f_attr_type = 'p';
        break;
    case S_IFLNK: // 链接文件
        pfile_attr->f_attr_type = 'l';
        break;
    case S_IFREG: // 普通文件
        pfile_attr->f_attr_type = '-';
        break;
    case S_IFSOCK: // 套接字文件
        pfile_attr->f_attr_type = 's';
        break;
    default:
        break;
    }
    return 0;
}

3.2. ls 命令设计与实现 (三)_获取文件权限

3.2.1. 设计⼀个获取⽂件权限的接⼝ get_file_permission, 具体如下:
int get_file_permission(file_attribute_t *pattr)
{
    // 获取文件的模式(st_mode),其中包括文件类型和权限信息
    mode_t mode = pattr->f_attr_stat_info.st_mode;

    // 循环变量 i 用来从 8 到 0 逐位检查文件的权限
    int i; 
    
    // index 用来指示当前权限字符存储在 f_attr_permission 数组的位置
    int index = 0;

    // perm[] 数组存储权限字符,对应 'r' -> read,'w' -> write,'x' -> execute
    char perm[] = {'r', 'w', 'x'};

    // 循环从第 8 位到第 0 位(共 9 位,分别对应三个权限:rwx,rwx,rwx)
    for (i = 8; i >= 0; i--)
    {
        // 右移 i 位后,按位与 0x1,检查该位是否为 1
        // (mode >> i) 将 mode 的第 i 位移到最低位,& 0x1 只保留最低位,用来检查该位是否为 1
        if ((mode >> i) & 0x1)
        {
            // 如果该位是 1(表示有该权限),则将对应的权限字符(r、w 或 x)赋值给 f_attr_permission[index]
            // index % 3 用来选择 'r', 'w', 'x' 中的一个字符,确保每三个字符循环一次
            pattr->f_attr_permission[index] = perm[index % 3];
        }
        else
        {
            // 如果该位是 0(表示没有该权限),则在 f_attr_permission[index] 位置存储 '-'
            pattr->f_attr_permission[index] = '-';
        }

        // index 每次递增,表示下一个权限字符存储到 f_attr_permission 数组的下一个位置
        index++;
    }

    // 返回 0 表示函数执行完毕
    return 0;
}
3.2.1.1. 代码解释:

mode_t mode = pattr->f_attr_stat_info.st_mode;

这行代码从文件的属性结构体 中获取 ,这个 值是一个包含文件权限信息的整数。pattrst_modemode

接下来,我们要通过这个 来提取文件的每一位权限,最终转换成字符(、、 或 )。moderwx-

int i;
int index = 0;
char perm[] = {'r', 'w', 'x'};

  • perm[] 数组保存了权限字符。它有三个元素:(读)、(写)和 (执行)。rwx
  • index 用来指示当前权限字符应该存储到 数组的哪个位置。f_attr_permission
  • i 用作循环变量。

for (i = 8; i >= 0; i--) {
if ((mode >> i) & 0x1) {
pattr->f_attr_permission[index] = perm[index % 3];
} else {
pattr->f_attr_permission[index] = '-';
}
index++;
}

3.2.1.2. for (i = 8; i >= 0; i--)
  • 这个循环从 开始,逐步递减到 ,总共 9 次。因为文件权限有 9 位(),每次循环处理一位权限。i = 8i = 0rwxrwxrwx
3.2.1.3. (mode >> i) & 0x1
  • mode >> i:将 中的第 位移到最低位。比如:modei
    • 当 时, 会把 中的第 8 位移到最低位。i = 8mode >> 8mode
    • 当 时, 会把第 7 位移到最低位,以此类推。i = 7mode >> 7
  • & 0x1:通过与运算,检查最低位是否为 1。如果是 1,表示该权限位启用,否则表示该权限位禁用。
    • 如果 的第 位是 1,表示该权限有效(比如,文件可以被读、写或执行),否则表示没有该权限。modei
3.2.1.4. pattr->f_attr_permission[index] = perm[index % 3];
  • index % 3: 会循环递增,表示当前正在处理第几个权限字符(、 或 )。 用来从 数组中选择对应的字符:indexrwxindex % 3perm[]
    • index % 3 == 0 时,选择 (读权限)。r
    • index % 3 == 1 时,选择 (写权限)。w
    • index % 3 == 2 时,选择 (执行权限)。x
  • 如果该位是 1(即权限启用),则把对应的权限字符(、 或 )存储到 中。rwxf_attr_permission[index]
3.2.1.5. pattr->f_attr_permission[index] = '-';
  • 如果该位是 0(即权限未启用),则在对应的位置存储 ,表示没有该权限。-
3.2.1.6. index++
  • index 每次增加 1,表示将下一个权限字符存储到数组的下一个位置。
3.2.2. 在 get_file_attribute 函数中进⾏调⽤
// 获取文件属性
int get_file_attr(file_attribute_t *attr,
                  const char *path,
                  const char *filename,
                  bool islink)
{
    if (NULL == attr || NULL == path || NULL == filename)
        return -1;
    int ret;
    if (islink)                                     // 如果是链接文件
                                                    // lstat() 获取链接文件属性函数
        ret = lstat(path, &attr->f_attr_stat_info); // 获取链接文件属性保存到自定义文件属性结构体中

    else
        ret = stat(path, &attr->f_attr_stat_info); // 获取普通文件属性保存到自定义文件属性结构体中

    if (ret == -1)
    {
        perror("[DEEPR]stat(): ");
        return -1;
    }
    get_file_type_ls(attr); // 获取文件类型
    get_file_permission(attr); // 获取文件权限
    return 0;
}

3.3. ls 命令设计与实现 (四)_获取⽤户名与⽤户组名

获取⽤户名与⽤户组名需要调⽤ getpwuid 函数与 getgrgid 函数, 根据 uid 与 gid 来获取对应

的⽤户名与⽤户组的名字

  • getpwuid 函数信息如下:
  • 函数头⽂件

#include <sys/types.h>

#include <pwd.h>

  • 函数原型

struct passwd *getpwuid(uid_t uid);

  • 函数功能

获取 ⽤户相关信息

  • 函数参数

uid : ⽤户 id

  • 函数返回值

成功 : 返回 struct passwd 指针

失败 : 返回 NULL,并设置 errno

struct passwd {

char *pw_name; /* 用户名 */

char *pw_passwd; /* 用户密码 */

uid_t pw_uid; /* 用户 ID */

gid_t pw_gid; /* 用户组 ID */

char *pw_gecos; /* 用户信息(通常是全名或者其他描述信息) */

char *pw_dir; /* 用户的主目录 */

char *pw_shell; /* 用户的默认 shell 程序 */

};

  • getgrgid函数信息如下:
  • 函数头⽂件

#include <sys/types.h>

#include <pwd.h>

  • 函数原型

struct group *getgrgid(gid_t gid);

  • 函数功能

获取 ⽤户组相关信息

函数参数

gid : ⽤户组 id

  • 函数返回值

成功 : 返回 struct group 指针

失败 : 返回 NULL,并设置 errno

struct group 结构体定义如下:

sstruct group {

char *gr_name; /* 组名 */

char *gr_passwd; /* 组密码 */

gid_t gr_gid; /* 组 ID */

char **gr_mem; /* 成员列表,NULL 终止的指针数组 */

};

  • 根据 uid 与 gid 来获取对应的⽤户名与⽤户组的名字

在stat函数中,f_attr_stat_info的结构体中保存着uid和gid,使用f_attr_stat_info.st_uid和f_attr_stat_info.st_gid 即可获取uid与gid

  • 设计接⼝ get_file_uname 并保存到 struct file_attribute 结构体中
int get_file_uname(file_attribute_t *pattr) // 获取文件所有者
{
    struct passwd *pwd = getpwuid(pattr->f_attr_stat_info.st_uid); // 获取文件所有者的用户信息
    strcpy(pattr->f_attr_uname, pwd->pw_name); // 将用户名复制到结构体的 f_attr_uname 字段
}
  • 设计接⼝ get_file_group 并保存到 struct file_attribute 结构体中
int get_file_group(file_attribute_t *pattr) // 获取文件所属组
{
    struct group *grp = getgrgid(pattr->f_attr_stat_info.st_gid); // 获取文件所属组的组信息
    strcpy(pattr->f_attr_gname, grp->gr_name); // 将组名复制到结构体的 f_attr_gname 字段
}
  • 在接⼝ get_file_attr 函数中进⾏调⽤
// 获取文件属性
int get_file_attr(file_attribute_t *attr,
                  const char *path,
                  const char *filename,
                  bool islink)
{
    if (NULL == attr || NULL == path || NULL == filename)
        return -1; // 检查输入参数是否为空,如果为空则返回错误
    int ret;
    if (islink)                                     // 如果是链接文件
                                                    // lstat() 获取链接文件属性函数
        ret = lstat(path, &attr->f_attr_stat_info); // 获取链接文件属性保存到自定义文件属性结构体中
    else
        ret = stat(path, &attr->f_attr_stat_info); // 获取普通文件属性保存到自定义文件属性结构体中

    if (ret == -1)
    {
        perror("[DEEPR]stat(): "); // 如果stat或lstat失败,打印错误信息
        return -1;                // 返回错误
    }
    get_file_type_ls(attr);    // 获取文件类型
    get_file_permission(attr); // 获取文件权限
    get_file_uname(attr);      // 获取文件所有者
    get_file_group(attr);      // 获取文件所属组
    return 0;                  // 成功完成,返回0
}

在 show_file_attribute 函数中打印

void show_file_attrbutes(file_attribute_t *attr) // 打印文件属性
{
    printf("File Name: %c ", attr->f_attr_type);      // 文件类型
    printf("%s ", attr->f_attr_permission);           // 文件权限
    printf(" %ld ", attr->f_attr_stat_info.st_nlink); // 硬链接数

    printf("%s ", attr->f_attr_uname); // 文件所有者
    printf("%s ", attr->f_attr_gname);  // 文件所属组
    putchar('\n'); // 空格
}

3.4. ls 命令设计与实现 (五)_获取文件大小

  • 获取⽂件⼤⼩可以直接通过 struct stat 中的 st_size 成员
  • 具体可以在 show_file_attribute 函数中进⾏打印即可

void show_file_attributes(struct file_attribute *pattr)

{

printf("File Name: %c ", attr->f_attr_type); // 文件类型

printf("%s ", attr->f_attr_permission); // 文件权限

printf(" %ld ", attr->f_attr_stat_info.st_nlink); // 硬链接数

printf("%s ", attr->f_attr_uname); // 文件所有者

printf("%s ", attr->f_attr_gname); // 文件所属组

printf(" %ld ",attr->f_attr_stat_info.st_size); // 文件大小

putchar('\n'); // 空格

}

获取最后修改时间需要在 struct stat 结构体中获取时间戳 ,通过 ctime () 或者 localtime ()

进⾏转换,具体实现如下:

  • 设计接⼝ get_file_last_modify_time 函数
void get_file_last_modify_time(file_attribute_t *pattr) // 获取文件最后修改时间
{
    char time_str[80];
    struct tm *local_time = localtime(&pattr->f_attr_stat_info.st_mtime); // 获取文件最后修改时间
    snprintf(time_str, sizeof(time_str), "%2d月 %02d日 %02d:%02d",
             local_time->tm_mon + 1, local_time->tm_mday,
             local_time->tm_hour, local_time->tm_min);// 将时间格式化为字符串
    strcpy(pattr->f_attr_mtime, time_str);            // 将时间字符串复制到结构体中
    
}
  • show_file_attribute 函数中进⾏调⽤
void show_file_attrbutes(file_attribute_t *attr) // 打印文件属性
{
    printf("File Name: %c ", attr->f_attr_type);      // 文件类型
    printf("%s ", attr->f_attr_permission);           // 文件权限
    printf(" %ld ", attr->f_attr_stat_info.st_nlink); // 硬链接数

    printf("%s ", attr->f_attr_uname);               // 文件所有者
    printf("%s ", attr->f_attr_gname);               // 文件所属组
    printf(" %ld ", attr->f_attr_stat_info.st_size); // 文件大小
    printf("%s ", attr->f_attr_mtime);    // 文件修改时间
    
    putchar('\n');                                   // 空格
}

3.5. ls 命令设计与实现 (六)_获取软连接

软链接⽂件⽐较特殊,需要进⾏特殊处理, 具体处理如下:

当需要获取链接⽂件的信息时,不能直接通过 stat 函数,需要调⽤ lstat 函数

在 get_file_attr 函数中要进⾏分开处理

int get_file_attr(struct file_attribute *pattr,const char *path,const cha

{

int ret;

if(islink)

ret = lstat(path,&pattr->f_attr_stat_info);

else

ret = stat(path,&pattr->f_attr_stat_info);

if(ret == -1)

{

perror("stat(): ");

return -1;

}

}

需要读取软链接⽂件所指向的⽂件,则需要使⽤ readlink 函数进⾏读取, 因为系统 ls 显示软链接是会显示具体内容

  • readlink 函数具体信息如下:

函数头⽂件

#include <unistd.h>

函数原型

ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);

函数功能

读取链接⽂件的内容,就是所指向的⽂件的⽂件名

函数参数

pathname : 路径名

buf : 缓冲区地址

bufsiz : 读取的最⼤⼤⼩

函数返回值

成功 : 读取的字节数

失败 : -1, 并设置 errno

在 get_file_attr 函数中的实现如下:

if(pattr->f_attr_type == 'l')

{

ret = readlink(path,pattr->f_attr_link_content,sizeof(pattr->f_attr_link_content);

if(ret == -1)

{

perror("readlink(): ");

return -1;

}

}

  • ⽂件名的输出,将命令输⼊的参数保存到相应 file_attribute_t 结构体中即可
  • 在 get_file_attr 中的输出:
int get_file_attr(file_attribute_t *attr,
                  const char *path,
                  const char *filename,
                  bool islink)
{
    if (NULL == attr || NULL == path || NULL == filename)
        return -1;
    int ret;
    if (islink)                                     // 如果是链接文件
                                                    // lstat() 获取链接文件属性函数
        ret = lstat(path, &attr->f_attr_stat_info); // 获取链接文件属性保存到自定义文件属性结构体中

    else
        ret = stat(path, &attr->f_attr_stat_info); // 获取普通文件属性保存到自定义文件属性结构体中

    if (ret == -1)
    {
        perror("[DEEPR]stat(): ");
        return -1;
    }
    if(attr->f_attr_type == 'l')  // 如果是软连接文件
    {
        ret = readlink(path,attr->f_attr_link_content,sizeof(attr->f_attr_link_content));
        if(ret == -1)
        {
            perror("readlink(): ");
            return -1;
        }
    }
    get_file_type_ls(attr);          // 获取文件类型
    get_file_permission(attr);       // 获取文件权限
    get_file__uname(attr);           // 获取文件所有者
    get_file_group(attr);            // 获取文件所属组
    get_file_last_modify_time(attr); // 获取文件修改时间

    
    strcpy(attr->f_attr_name,filename);  // 获取文件名
    return 0;
}
  • 在 show_file_attribute 函数中进⾏打印
void show_file_attrbutes(file_attribute_t *attr) // 打印文件属性
{
    printf("File Name: %c ", attr->f_attr_type);      // 文件类型
    printf("%s ", attr->f_attr_permission);           // 文件权限
    printf(" %ld ", attr->f_attr_stat_info.st_nlink); // 硬链接数

    printf("%s ", attr->f_attr_uname);               // 文件所有者
    printf("%s ", attr->f_attr_gname);               // 文件所属组
    printf(" %ld  ", attr->f_attr_stat_info.st_size); // 文件大小
    printf("%s ", attr->f_attr_mtime);               // 文件修改时间

    if(attr->f_attr_type == 'l')  // 如果是软连接文件
    printf(" %s->%s \n",attr->f_attr_name,attr->f_attr_link_content); // 打印链接文件内容
    printf(" %s \n",attr->f_attr_name);          // 打印文件名
    putchar('\n'); // 空格
}
  • 完整ls代码:
#include "cmd_ls.h"

// ls -l <path>
int cmd_ls_execute(cmd_t *pcmd) // pcmd 得到ls命令的内容
{
    if (NULL == pcmd)
        return -1;                // 判断自定义结构体是否为空
    if (pcmd->cmd_arg_count != 2) // 判断命令参数个数是否为2
    {
        fprintf(stderr, "Command argument Error.\n");
        return -1;
    }

    if (pcmd->cmd_arg_list[1] != NULL)                    // 判断命令参数1是否为空
        return cmd_list_directory(pcmd->cmd_arg_list[1]); // 调用遍历目录函数
    else
        return -1;

    int ret = 0;
#ifdef DEBUG
    printf("cmd_ls_execute\n");
#endif

    return 0;
}

// 遍历目录
int cmd_list_directory(const char *dirpath) 
{
    DIR *pdir = NULL;              // 目录指针
    struct dirent *pdirent = NULL; // 目录项指针
    char path[128] = {0};          // 用于存储文件的完整路径
    file_attribute_t f_attr;       // 自定义文件属性结构体

    pdir = opendir(dirpath); // 打开目录
    if (pdir == NULL)
    {
        perror("open(): "); // 如果打开目录失败,打印错误信息
        return -1;
    }

    // 读取目录中的每个条目
    while ((pdirent = readdir(pdir)) != NULL) 
    { 
        // 跳过当前目录(.)和父目录(..)
        if (strcmp(pdirent->d_name, ".") == 0 || strcmp(pdirent->d_name, "..") == 0)
            continue;

#ifdef DEBUG
        printf("[DEBUG]: pdirent->d_name %s\n", pdirent->d_name); // 输出目录项名称
#endif

        memset(&f_attr, 0, sizeof(f_attr));           // 清空自定义文件属性结构体
        make_path_ls(path, dirpath, pdirent->d_name); // 合成具体文件的路径

        // 根据文件类型获取文件属性
        if (pdirent->d_type == DT_LNK) // 如果是软链接文件
            get_file_attr(&f_attr, path, pdirent->d_name, true); // 获取链接文件属性
        else
            get_file_attr(&f_attr, path, pdirent->d_name, false); // 获取普通文件属性

        show_file_attrbutes(&f_attr); // 打印文件属性
    }

    closedir(pdir); // 关闭目录
    return 0;
}

// 获取文件属性
int get_file_attr(file_attribute_t *attr,
                  const char *path,
                  const char *filename,
                  bool islink)
{
    if (NULL == attr || NULL == path || NULL == filename)
        return -1; // 参数检查
    int ret;

    // 根据是否是链接文件选择不同的函数获取文件属性
    if (islink) // 如果是链接文件
        ret = lstat(path, &attr->f_attr_stat_info); // 使用 lstat 获取链接文件的属性
    else
        ret = stat(path, &attr->f_attr_stat_info); // 使用 stat 获取普通文件的属性

    if (ret == -1)
    {
        perror("[DEEPR]stat(): "); // 如果获取失败,打印错误信息
        return -1;
    }

    // 如果是链接文件,获取链接的目标内容
    if(attr->f_attr_type == 'l')  
    {
        ret = readlink(path, attr->f_attr_link_content, sizeof(attr->f_attr_link_content));
        if(ret == -1)
        {
            perror("readlink(): "); // 如果获取链接内容失败,打印错误信息
            return -1;
        }
    }

    // 获取文件的各种属性
    get_file_type_ls(attr);          // 获取文件类型
    get_file_permission(attr);       // 获取文件权限
    get_file__uname(attr);           // 获取文件所有者
    get_file_group(attr);            // 获取文件所属组
    get_file_last_modify_time(attr); // 获取文件修改时间

    strcpy(attr->f_attr_name, filename);  // 获取文件名
    return 0;
}

// 打印文件属性
void show_file_attrbutes(file_attribute_t *attr) 
{
    // 按照 ls -l 的格式打印文件属性
    printf("File Name: %c ", attr->f_attr_type);      // 文件类型
    printf("%s ", attr->f_attr_permission);           // 文件权限
    printf(" %ld ", attr->f_attr_stat_info.st_nlink); // 硬链接数

    printf("%s ", attr->f_attr_uname);               // 文件所有者
    printf("%s ", attr->f_attr_gname);               // 文件所属组
    printf(" %ld  ", attr->f_attr_stat_info.st_size); // 文件大小
    printf("%s ", attr->f_attr_mtime);               // 文件修改时间

    // 如果是软链接文件,打印链接的目标
    if(attr->f_attr_type == 'l')  
        printf(" %s->%s \n", attr->f_attr_name, attr->f_attr_link_content); 
    printf(" %s \n", attr->f_attr_name);          // 打印文件名
    putchar('\n'); // 换行
}

// 合成具体文件的路径
int make_path_ls(char *path, const char *dirpath, const char *filename)
{
    strcpy(path, dirpath); // 复制目录路径
    strcat(path, "/");     // 添加路径分隔符
    strcat(path, filename); // 添加文件名

#ifdef DEBUG
    printf("[DEBUG] make_path_ls: %s\n", path); // 调试信息,检查合成的路径
#endif
    return 0;
}

// 获取文件类型
int get_file_type_ls(struct file_attribute *pattr)
{
    mode_t mode = pattr->f_attr_stat_info.st_mode; // 获取文件模式

    // 根据文件模式判断文件类型
    switch (mode & S_IFMT) 
    {
    case S_IFBLK:
        pattr->f_attr_type = 'b'; // 块设备文件
        break;

    case S_IFCHR:
        pattr->f_attr_type = 'c'; // 字符设备文件
        break;

    case S_IFDIR:
        pattr->f_attr_type = 'd'; // 目录
        break;

    case S_IFIFO:
        pattr->f_attr_type = 'p'; // FIFO 文件
        break;

    case S_IFLNK:
        pattr->f_attr_type = 'l'; // 符号链接文件
        break;

    case S_IFREG:
        pattr->f_attr_type = '-'; // 普通文件
        break;

    case S_IFSOCK:
        pattr->f_attr_type = 's'; // 套接字文件
        break;

    default:
        break;
    }

    return 0;
}

// 获取文件权限
int get_file_permission(file_attribute_t *pattr)
{
    mode_t mode = pattr->f_attr_stat_info.st_mode; // 获取文件模式

    int i;
    int index = 0;
    char perm[] = {'r', 'w', 'x'}; // 权限字符数组

    // 遍历文件模式的每一位,判断权限
    for (i = 8; i >= 0; i--)
    {
        if ((mode >> i) & 0x1) // 将高位的值移动到最底位,在与0x1进行与运算,如果结果为1,则表示该位为1
            pattr->f_attr_permission[index] = perm[index % 3]; // 将权限字符保存到数组中
        else
            pattr->f_attr_permission[index] = '-'; // 如果没有权限,用 '-' 表示
        index++;
    }

    return 0;
}

// 获取文件所有者
int get_file__uname(file_attribute_t *pattr) 
{
    struct passwd *pwd = getpwuid(pattr->f_attr_stat_info.st_uid); // 根据用户ID获取用户信息
    strcpy(pattr->f_attr_uname, pwd->pw_name); // 将用户名复制到结构体中
}

// 获取文件所属组
int get_file_group(file_attribute_t *pattr) 
{
    struct group *grp = getgrgid(pattr->f_attr_stat_info.st_gid); // 根据组ID获取组信息
    strcpy(pattr->f_attr_gname, grp->gr_name); // 将组名复制到结构体中
}

// 获取文件最后修改时间
void get_file_last_modify_time(file_attribute_t *pattr) 
{
    char time_str[80]; // 用于存储时间字符串
    struct tm *local_time = localtime(&pattr->f_attr_stat_info.st_mtime); // 获取文件最后修改时间

    // 将时间格式化为字符串
    snprintf(time_str, sizeof(time_str), "%02d月 %02d日 %02d:%02d",
             local_time->tm_mon + 1, local_time->tm_mday,
             local_time->tm_hour, local_time->tm_min);

    strcpy(pattr->f_attr_mtime, time_str); // 将时间字符串复制到结构体中
}

书写不易,点个赞吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/961569.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

idea修改模块名导致程序编译出错

本文简单描述分别用Idea菜单、pom.xml文件管理项目模块module 踩过的坑&#xff1a; 通过idea菜单创建模块&#xff0c;并用idea菜单修改模块名&#xff0c;结构程序编译报错&#xff0c;出错的代码莫名奇妙。双击maven弹窗clean时&#xff0c;还是报错。因为模块是新建的&am…

C27.【C++ Cont】时间、空间限制和STL库的简单了解

&#x1f9e8;&#x1f9e8;&#x1f9e8;&#x1f9e8;&#x1f9e8;&#x1f9e8;&#x1f9e8;&#x1f9e8;&#x1f9e8;春节篇&#x1f9e8;&#x1f9e8;&#x1f9e8;&#x1f9e8;&#x1f9e8;&#x1f9e8;&#x1f9e8;&#x1f9e8;&#x1f9e8; 目录 1.竞赛中的…

ResNet 残差网络

目录 网络结构 残差块&#xff08;Residual Block&#xff09; ResNet网络结构示意图 残差块&#xff08;Residual Block&#xff09;细节 基本残差块&#xff08;ResNet-18/34&#xff09; Bottleneck残差块&#xff08;ResNet-50/101/152&#xff09; 残差连接类型对比 变体网…

组件框架漏洞

一.基础概念 1.组件 定义&#xff1a;组件是软件开发中具有特定功能或特性的可重用部件或模块&#xff0c;能独立使用或集成到更大系统。 类型 前端 UI 组件&#xff1a;像按钮、下拉菜单、导航栏等&#xff0c;负责构建用户界面&#xff0c;提升用户交互体验。例如在电商 AP…

电脑无法开机,重装系统后没有驱动且驱动安装失败

电脑无法开机&#xff0c;重装系统后没有驱动且驱动安装失败 前几天电脑突然坏了&#xff0c;电脑卡住后&#xff0c;强制关机&#xff0c;再开机后开机马上就关机。尝试无数次开机后失败&#xff0c;进入BIOS界面&#xff0c;发现已经没有Windows系统了。重新安装系统后&…

NLP自然语言处理通识

目录 ELMO 一、ELMo的核心设计理念 1. 静态词向量的局限性 2. 动态上下文嵌入的核心思想 3. 层次化特征提取 二、ELMo的模型结构与技术逻辑 1. 双向语言模型&#xff08;BiLM&#xff09; 2. 多层LSTM的层次化表示 三、ELMo的运行过程 1. 预训练阶段 2. 下游任务微调 四、ELMo的…

二进制安卓清单 binary AndroidManifest - XCTF apk 逆向-2

XCTF 的 apk 逆向-2 题目 wp&#xff0c;这是一道反编译对抗题。 题目背景 AndroidManifest.xml 在开发时是文本 xml&#xff0c;在编译时会被 aapt 编译打包成为 binary xml。具体的格式可以参考稀土掘金 MindMac 做的类图&#xff08;2014&#xff09;&#xff0c;下面的博…

Mac Electron 应用签名(signature)和公证(notarization)

在MacOS 10.14.5之后&#xff0c;如果应用没有在苹果官方平台进行公证notarization(我们可以理解为安装包需要审核&#xff0c;来判断是否存在病毒)&#xff0c;那么就不能被安装。当然现在很多人的解决方案都是使用sudo spctl --master-disable&#xff0c;取消验证模式&#…

stack 和 queue容器的介绍和使用

1.stack的介绍 1.1stack容器的介绍 stack容器的基本特征和功能我们在数据结构篇就已经详细介绍了&#xff0c;还不了解的uu&#xff0c; 可以移步去看这篇博客哟&#xff1a; 数据结构-栈数据结构-队列 简单回顾一下&#xff0c;重要的概念其实就是后进先出&#xff0c;栈在…

【Rust自学】15.0. 智能指针(序):什么是智能指针及Rust智能指针的特性

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 15.0.1 指针的基本概念 指针是一个变量在内存中包含的是一个地址&#xff0c;指向另一个数据。 Rust 中最常见的指针是引用&#xff0c…

单调栈算法

文章目录 题目概述题目详解739.每日温度1475.商品折扣后的最终价格84.柱状图中最大的矩形 题目概述 单调栈&#xff1a;栈&#xff0c;并且栈是有序的 单调栈的两种写法&#xff1a; 左 -> 右&#xff0c;或者右 -> 左 建议使用左到右的写法 及时去掉无用元素&#xff0c…

vue-有关于TS与路由器

title: vue(TS)路由器 date: 2025-01-28 12:00:00 tags:- 前端 categories:- 前端Vue3-第二部分 这里是代码中出现TS的&#xff0c;后面是路由器 现在先上代码&#xff0c;步步分析。 eg1-props的使用 步步分析代码&#xff08;先理解&#xff0c;再实践&#xff09; 框架…

【AI编辑器】字节跳动推出AI IDE——Trae,专为中文开发者深度定制

目录 一、背景 二、核心特性 2.1 AI驱动的代码自动生成 2.2 智能问答与代码补全 2.3 多语言支持 2.4 插件与扩展 三、架构 四、下载使用 4.1 下载与安装 4.2 界面与配置 五、应用实践 5.1 快速生成代码 5.2 智能问答与调试 5.3 团队协作与代码审查 六、与Cursor…

(done) ABI 相关知识补充:内核线程切换、用户线程切换、用户内核切换需要保存哪些寄存器?

由于操作系统和编译器约定了 ABI&#xff0c;如下&#xff1a; 编译器在对 C 语言编译时&#xff0c;会自动 caller 标注的寄存器进行保存恢复。保存的步骤通常发生在进入函数的时候&#xff0c;恢复的步骤通常发生在从函数返回的时候。 内核线程切换需要保存的寄存器&#…

把本地搭建的hexo博客部署到自己的服务器上

配置远程服务器的git 安装git 安装依赖工具包 yum install -y curl-devel expat-devel gettext-devel openssl-devel zlib-devel安装编译工具 yum install -y gcc perl-ExtUtils-MakeMaker package下载git&#xff0c;也可以去官网下载了传到服务器上 wget https://www.ke…

71-《颠茄》

颠茄 颠茄&#xff0c;别名&#xff1a;野山茄、美女草、别拉多娜草&#xff0c;拉丁文名&#xff1a;Atropa belladonna L.是双子叶植物纲、茄科、颠茄属多年生草本&#xff0c;或因栽培为一年生&#xff0c;根粗壮&#xff0c;圆柱形。茎下部单一&#xff0c;带紫色&#xff…

二次封装的方法

二次封装 我们开发中经常需要封装一些第三方组件&#xff0c;那么父组件应该怎么传值&#xff0c;怎么调用封装好的组件原有的属性、插槽、方法&#xff0c;一个个调用虽然可行&#xff0c;但十分麻烦&#xff0c;我们一起来看更简便的方法。 二次封装组件&#xff0c;属性怎…

计算机组成原理(2)王道学习笔记

数据的表示和运算 提问&#xff1a;1.数据如何在计算机中表示&#xff1f; 2.运算器如何实现数据的算术、逻辑运算&#xff1f; 十进制计数法 古印度人发明了阿拉伯数字&#xff1a;0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;6&#…

npm启动前端项目时报错(vue) error:0308010C:digital envelope routines::unsupported

vue 启动项目时&#xff0c;npm run serve 报下面的错&#xff1a; error:0308010C:digital envelope routines::unsupported at new Hash (node:internal/crypto/hash:67:19) at Object.createHash (node:crypto:133:10) at FSReqCallback.readFileAfterClose [as on…

「 机器人 」仿生扑翼飞行器中的“被动旋转机制”概述

前言 在仿生扑翼飞行器的机翼设计中,模仿昆虫翼的被动旋转机制是一项关键技术。其核心思想在于:机翼旋转角度(攻角)并非完全通过主动伺服来控制,而是利用空气动力和惯性力的作用,自然地实现被动调节。以下对这种设计的背景、原理与优势进行详细说明。 1. 背景:昆虫的被动…