实现简单的ls

实现简单的ls

ls 是 GNU/Linux 操作系统中常见的命令。 请使用 C 语言,基于 GNU/Linux 操作系统,编程实现 ls 命令的部分功能。

要求
实现 ls 的 -a、-l、-R、-t、-r、-i、-s 参数,并允许这些参数任意组合。
-R 需要对 / 的遍历测试。
界面美观(输出对齐、带颜色显示等)。

思路

首先要进行参数解析,要确定有哪些参数,有哪些文件,再依照参数对文件进行相应的打印,其中参数-t,-r需要对目录下的文件进行排序,所以要先将目录下的文件读取后存储,再进行排序,通过申请结构体数组进行实现。

typedef struct {
    char *path; //申请结构体数组,将目录打开后的文件存入path中,先排序在打印
    struct stat st;//读取文件信息
} Entry;

递归的思路是让其重新进入函数,简单点讲就是把之前步骤再做一遍。函数process_dir和process_file分别对应不同的操作,process_dir进行排序和递归,process_file进行直接打印。

void process_path(const char *path, Options opts, int is_explicit_dir) {
    struct stat st;
    if (lstat(path, &st) == -1) {
        perror(path);
        return;
    }

    if (S_ISDIR(st.st_mode)) //判断是否为目录
    {
        process_dir(path, opts, is_explicit_dir);
    } else //是文件 
    {
        process_file(path, opts);
    }
}

知识储备

掌握基本的目录操作,如结构体stat,dirent的使用。
struct stat 结构体定义在 <sys/stat.h> 头文件中,用于存储文件的状态信息。当你需要获取文件的元数据(如大小、权限、所有者等)时,可以使用 stat、fstat 或 lstat 函数来填充这个结构体。

#include <sys/stat.h>
 
struct stat {
    dev_t     st_dev;     // 设备ID
    ino_t     st_ino;     // 节点号
    mode_t    st_mode;    // 文件类型和权限
    nlink_t   st_nlink;  // 硬链接数
    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;  // 文件占用的块数
    time_t    st_atime;   // 上次访问时间
    time_t    st_mtime;   // 上次修改时间
    time_t    st_ctime;   // 上次状态改变时间
};

struct dirent 结构体定义在 <dirent.h> 头文件中,用于存储目录项的信息。当你需要读取目录中的文件和子目录时,可以使用 opendir、readdir 和 closedir 函数来操作目录流,并获取 struct dirent 结构体。

#include <dirent.h>
 
struct dirent {
    ino_t          d_ino;       // 节点号
    off_t          d_off;       // 目录文件偏移量
    unsigned short d_reclen;    // 结构体长度
    unsigned char  d_type;      // 文件类型
    char           d_name[256]; // 文件名
};

以及库函数的使用,库函数可以极大的提高代码的效率以及便捷性,如strrchr,snprintf,strdup。
在C语言中,你可以使用宏定义来简化ANSI转义序列的使用,从而使代码更加清晰和易于维护。以下是一个使用宏定义来实现彩色文本输出的示例:

#include <stdio.h>
 
// 定义颜色宏
#define RESET   "\033[0m"
#define BLACK   "\033[30m"      /* 黑色 */
#define RED     "\033[31m"      /* 红色 */
#define GREEN   "\033[32m"      /* 绿色 */
#define YELLOW  "\033[33m"      /* 黄色 */
#define BLUE    "\033[34m"      /* 蓝色 */
#define MAGENTA "\033[35m"      /* 洋红色 */
#define CYAN    "\033[36m"      /* 青色 */
#define WHITE   "\033[37m"      /* 白色 */
 
int main() {
    printf(RED "This text is red!" RESET "\n");
    printf(GREEN "This text is green!" RESET "\n");
    printf(YELLOW "This text is yellow!" RESET "\n");
    printf(BLUE "This text is blue!" RESET "\n");
    printf(MAGENTA "This text is magenta!" RESET "\n");
    printf(CYAN "This text is cyan!" RESET "\n");
    printf(WHITE "This text is white!" RESET "\n");
 
    return 0;
}

代码实现

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <unistd.h>
#include <limits.h>
#include <locale.h>

#define COLOR_RESET "\x1B[0m"
#define COLOR_DIR "\x1B[34m"
#define COLOR_LINK "\x1B[36m"
#define COLOR_EXE "\x1B[32m"

typedef struct {
    int a, l, R, t, r, i, s;
    int dir_count;  // 需要显示目录名的目录数量
} Options;

typedef struct {
    char *path; //申请结构体数组,将目录打开后的文件存入path中,先排序在打印
    struct stat st;//读取文件信息
} Entry;

// 新增比较函数:按文件名排序
int compare_name(const void *a, const void *b) {
    const Entry *ea = a;
    const Entry *eb = b;
    const char *name_a = strrchr(ea->path, '/') ? strrchr(ea->path, '/') + 1 : ea->path;// 字符串中查找最后一个出现的 '/' 字符
    const char *name_b = strrchr(eb->path, '/') ? strrchr(eb->path, '/') + 1 : eb->path;
    return strcoll(name_a, name_b);//比较字符串的函数
}
//根据文件类型返回相应的颜色。
const char* get_color(const char *path) {
    struct stat st;
    if (lstat(path, &st) == -1) return COLOR_RESET;

    if (S_ISLNK(st.st_mode)) return COLOR_LINK;
    if (S_ISDIR(st.st_mode)) return COLOR_DIR;
    if (st.st_mode & S_IXUSR) return COLOR_EXE;
    return COLOR_RESET;
}
//将文件模式转换为字符串表示。
char* mode_to_str(mode_t mode, char *str) {
    strcpy(str, "----------");
    if (S_ISDIR(mode)) str[0] = 'd';
    if (S_ISLNK(mode)) str[0] = 'l';
    if (S_ISCHR(mode)) str[0] = 'c';
    if (S_ISBLK(mode)) str[0] = 'b';
    if (S_ISFIFO(mode)) str[0] = 'p';
    if (S_ISSOCK(mode)) str[0] = 's';

    if (mode & S_IRUSR) str[1] = 'r';
    if (mode & S_IWUSR) str[2] = 'w';
    if (mode & S_IXUSR) str[3] = 'x';
    if (mode & S_IRGRP) str[4] = 'r';
    if (mode & S_IWGRP) str[5] = 'w';
    if (mode & S_IXGRP) str[6] = 'x';
    if (mode & S_IROTH) str[7] = 'r';
    if (mode & S_IWOTH) str[8] = 'w';
    if (mode & S_IXOTH) str[9] = 'x';
    return str;
}
//按修改时间排序的比较函数。
int compare_entries(const void *a, const void *b) {
    const Entry *ea = a, *eb = b;
    if (ea->st.st_mtime == eb->st.st_mtime)
        return strcoll(ea->path, eb->path);
    return (ea->st.st_mtime > eb->st.st_mtime) ? -1 : 1;
}
//打印文件或目录的详细信息。
void print_entry(const char *path, const char *name, Options opts) {
    struct stat st;
    if (lstat(path, &st) == -1) return;

    if (opts.i) printf("%8lu ", st.st_ino);
    if (opts.s) printf("%4ld ", st.st_blocks/2);

    if (opts.l) {
        char modestr[11];
        mode_to_str(st.st_mode, modestr);
        printf("%s %2ld %-8s %-8s %8ld ",
               modestr,
               st.st_nlink,
               getpwuid(st.st_uid)->pw_name,
               getgrgid(st.st_gid)->gr_name,
               st.st_size);

        char timebuf[80];
        strftime(timebuf, sizeof(timebuf), "%b %d %H:%M", localtime(&st.st_mtime));
        printf("%s ", timebuf);
    }

    const char *color = get_color(path);
    printf("%s%s%s", color, name, COLOR_RESET);
    //检查一个文件是否为符号链接,并打印出该符号链接指向的目标路径
    if (opts.l && S_ISLNK(st.st_mode)) {
        char linkbuf[PATH_MAX];
        ssize_t len = readlink(path, linkbuf, sizeof(linkbuf)-1);
        if (len != -1) {
            linkbuf[len] = '\0';
            printf(" -> %s", linkbuf);
        }
    }
    putchar('\n');
}

void process_path(const char *path, Options opts, int is_explicit_dir);
//处理目录,包括读取目录内容、排序、打印,并递归处理子目录(如果指定了-R选项)。
void process_dir(const char *dirpath, Options opts, int is_explicit_dir) {
    if (opts.dir_count++ > 0) putchar('\n');
    if (is_explicit_dir) printf("%s:\n", dirpath);

    DIR *dir = opendir(dirpath);
    if (!dir) {
        perror(dirpath);
        return;
    }

    Entry *entries = NULL;//可以看作申请了结构体Entry数组
    int count = 0;
    struct dirent *ent;
    //先把目录中的文件存入结构体path中,最后进入函数print_entry打印
    while ((ent = readdir(dir)) != NULL) {
        if (!opts.a && ent->d_name[0] == '.') continue;//用于处理隐藏文件

        char fullpath[PATH_MAX];
        if(strcmp(dirpath,"/")==0)
        {
            snprintf(fullpath, sizeof(fullpath), "/%s",  ent->d_name);//补充路径
        }
        else{
            snprintf(fullpath, sizeof(fullpath), "%s/%s", dirpath, ent->d_name);//补充路径
        }
        // snprintf(fullpath, sizeof(fullpath), "%s/%s", dirpath, ent->d_name);//补充路径
        entries = realloc(entries, (count+1)*sizeof(Entry));
        entries[count].path = strdup(fullpath);//用于复制一个字符串到新的内存空间
        lstat(fullpath, &entries[count].st);//将文件打开读取信息
        count++;
    }
    closedir(dir);
    //如果参数存在-t,-r,先进行排序
    if (opts.t) {
        qsort(entries, count, sizeof(Entry), compare_entries);
    } 
    else {
        qsort(entries, count, sizeof(Entry), compare_name);
    }
    if (opts.r) {
        for (int i = 0; i < count/2; i++) {
            Entry tmp = entries[i];
            entries[i] = entries[count-1-i];
            entries[count-1-i] = tmp;
        }
    }
    //依次进入print_entry打印
    for (int i = 0; i < count; i++) 
    {
        //用于查找一个字符在字符串中最后一次出现的位置,并返回指向该字符的指针及其后面的部分
        char *name = strrchr(entries[i].path, '/') + 1;
        print_entry(entries[i].path, name, opts);
    }
    //如果存在参数-R,进入process_path再进入该函数
    if (opts.R) {
        for (int i = 0; i < count; i++) {
            //判断是否为目录并且跳过当前目录和上级目录
            if (S_ISDIR(entries[i].st.st_mode) && 
                strcmp(entries[i].path + strlen(dirpath)+1, ".") != 0 &&
                strcmp(entries[i].path + strlen(dirpath)+1, "..") != 0) 
            {
                process_path(entries[i].path, opts, 1);
            }
        }
    }
    //释放内存
    for (int i = 0; i < count; i++) free(entries[i].path);
    free(entries);
}
//处理文件,即打印文件信息。
void process_file(const char *path, Options opts) {
    print_entry(path, path, opts);
}
//根据路径指向的是文件还是目录,调用process_file或process_dir。
void process_path(const char *path, Options opts, int is_explicit_dir) {
    struct stat st;
    if (lstat(path, &st) == -1) {
        perror(path);
        return;
    }

    if (S_ISDIR(st.st_mode)) //判断是否为目录
    {
        process_dir(path, opts, is_explicit_dir);
    } else //是文件 
    {
        process_file(path, opts);
    }
}

int main(int argc, char **argv) {
    setlocale(LC_ALL, "");//用来设置程序的当前区域设置(locale)为环境变量所指定的值。
    Options opts = {0};//初始化
    char **paths = NULL;
    int path_count = 0;

    // 参数解析
    for (int i = 1; i < argc; i++) 
    {
        if (argv[i][0] == '-') {
            char *p = argv[i] + 1;//跳过-,读后面字母
            while (*p) {
                switch (*p) {
                    case 'a': opts.a = 1; break;
                    case 'l': opts.l = 1; break;
                    case 'R': opts.R = 1; break;
                    case 't': opts.t = 1; break;
                    case 'r': opts.r = 1; break;
                    case 'i': opts.i = 1; break;
                    case 's': opts.s = 1; break;
                    default: 
                        fprintf(stderr, "无效选项: -%c\n", *p);
                        exit(EXIT_FAILURE);
                }
                p++;
            }
        } 
        else //如果后面是文件路径,存入paths中
        {
            paths = realloc(paths, (path_count+1)*sizeof(char*));
            paths[path_count++] = argv[i];
        }
    }

    // 默认处理当前目录
    if (path_count == 0) {
        paths = realloc(paths, sizeof(char*));
        paths[path_count++] = ".";
    }

    // 处理多个路径
    opts.dir_count = 0;
    for (int i = 0; i < path_count; i++) {
        process_path(paths[i], opts, path_count > 1);
    }

    free(paths);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值