[{"data":1,"prerenderedAt":314},["ShallowReactive",2],{"content:\u002F2023\u002Fzls":3,"surround:\u002F2023\u002Fzls":303},{"id":4,"title":5,"body":6,"categories":277,"date":279,"description":280,"draft":281,"extension":282,"image":283,"meta":284,"navigation":286,"path":287,"permalink":283,"published":283,"readingTime":288,"recommend":283,"references":283,"seo":293,"sitemap":294,"stem":295,"tags":296,"type":300,"updated":301,"__hash__":302},"content\u002Fposts\u002F2023\u002Fzls.md","Linux 下简单 ls 命令的实现",{"type":7,"value":8,"toc":263},"minimark",[9,13,17,90,100,103,108,117,131,138,142,153,175,179,190,198,202,212,216,228,232,249,255],[10,11,12],"h2",{"id":12},"功能",[14,15,16],"p",{},"该程序的功能包括：",[18,19,20,24,27,30,33],"ul",{},[21,22,23],"li",{},"列出指定目录下的所有文件和子目录",[21,25,26],{},"对列表进行排序，支持按照时间、名称等进行排序",[21,28,29],{},"支持展示文件的名称、大小、权限、修改时间等元信息",[21,31,32],{},"支持展示文件inode号和文件占用块数量（单位为4KB）",[21,34,35,36],{},"该程序还支持的参数有：\n",[18,37,38,48,55,62,69,76,83],{},[21,39,40,47],{},[41,42,45],"code",{"className":43,"code":45,"language":46},[44],"language-js","-a","js","：列出所有文件和子目录，包括隐藏文件和文件夹",[21,49,50,54],{},[41,51,53],{"className":52,"code":53,"language":46},[44],"-l","：展示详细的文件元信息，包括文件权限、大小、修改时间、占用块数量、链接数等",[21,56,57,61],{},[41,58,60],{"className":59,"code":60,"language":46},[44],"-R","：递归展示子目录下的文件信息",[21,63,64,68],{},[41,65,67],{"className":66,"code":67,"language":46},[44],"-t","：按照时间进行排序",[21,70,71,75],{},[41,72,74],{"className":73,"code":74,"language":46},[44],"-r","：逆序排序",[21,77,78,82],{},[41,79,81],{"className":80,"code":81,"language":46},[44],"-i","：展示文件的inode号",[21,84,85,89],{},[41,86,88],{"className":87,"code":88,"language":46},[44],"-s","：展示文件的占用块数量（单位为512字节）",[14,91,92,93,99],{},"使用该程序，只需要在终端中输入 ",[41,94,97],{"className":95,"code":97,"language":98},[96],"language-sh",".\u002Fzls","sh"," 命令即可，默认情况下，它会展示当前所在目录下的所有文件和目录。如果想要指定一个目录进行展示，可以在命令中输入要展示的目录名。",[10,101,102],{"id":102},"函数",[104,105,107],"h3",{"id":106},"list-函数","list 函数",[14,109,110,116],{},[41,111,114],{"className":112,"code":114,"language":115},[113],"language-c","list()","c"," 是最核心的函数，负责展示指定目录下的文件列表信息。该函数主要的实现逻辑包括：",[18,118,119,122,125,128],{},[21,120,121],{},"首先打开指定目录，并读取目录下的所有文件和子目录",[21,123,124],{},"对读取到的文件和子目录进行排序",[21,126,127],{},"针对每一个文件和目录，展示它们的信息",[21,129,130],{},"在实现过程中，需要注意：如果该目录无法打开，说明该目录可能不存在或者无权访问，此时需要检查该路径是否为一个文件，如果是，则展示该文件的信息。",[14,132,133,134,137],{},"此外，如果参数中包含 ",[41,135,60],{"className":136,"code":60,"language":115},[113]," 参数，则需要递归展示子目录下的文件列表。具体实现时，可以在每次读取到一个子目录时，调用 list 函数进行递归展示。",[104,139,141],{"id":140},"listfile-函数","listfile 函数",[14,143,144,148,149,152],{},[41,145,147],{"className":146,"code":147,"language":115},[113],"listfile()"," 是 ",[41,150,114],{"className":151,"code":114,"language":115},[113]," 中最核心的输出函数。该函数用于展示文件的信息，包括名称、大小、权限、修改时间等。使用 ANSI 控制字符可以实现对输出文本颜色的改变。",[14,154,155,156,159,160,164,165,169,170,174],{},"在实现过程中，需要注意：如果参数中包含 ",[41,157,53],{"className":158,"code":53,"language":46},[44]," 参数，则需要展示详细的文件元信息。此时，需要通过调用 ",[41,161,163],{"className":162,"code":163,"language":115},[113],"mod2str()"," 函数将文件权限码转化为字符串，并通过 ",[41,166,168],{"className":167,"code":168,"language":115},[113],"uname()"," 和 ",[41,171,173],{"className":172,"code":173,"language":115},[113],"gname()"," 将用户id和组id转化为用户名和组名。",[104,176,178],{"id":177},"parseparam-函数","parseParam 函数",[14,180,181,185,186,189],{},[41,182,184],{"className":183,"code":184,"language":115},[113],"parseParam()"," 函数负责解析命令行参数，并设置对应的展示 flag，以便在展示文件列表时能够根据 flag 进行展示。其核心实现逻辑为：针对每一个传入参数，检查其首字符是否为 ",[41,187,188],{"code":188},"-","，如果是则解析其中包含的flag。",[14,191,192,193,197],{},"在实现过程中，这里使用了一个宏定义，用于简化代码。不同的参数对应不同的 flag，因此这里使用了二进制位运算，每个 flag 对应一个二进制位，当设置 flag 后把对应二进制位置为 ",[41,194,196],{"className":195,"code":196,"language":115},[113],"1","。使用位运算可以将多个 flag 组合在一起传入。",[104,199,201],{"id":200},"mod2str-函数","mod2str 函数",[14,203,204,207,208,211],{},[41,205,163],{"className":206,"code":163,"language":115},[113]," 用于将文件权限码转化为字符串。权限编码由三个八进制数字组成，每个数字对应一个权限，分别为所有者、同组用户、其他用户。具体实现时，可以通过判断文件类型和各个权限位来将它们转化为字符串。返回的字符串长度为 ",[41,209,210],{"code":210},"10","。",[104,213,215],{"id":214},"num2str-函数","num2str 函数",[14,217,218,222,223,227],{},[41,219,221],{"className":220,"code":221,"language":115},[113],"num2str()"," 函数用于将数字转化为字符串。该函数中使用了 ",[41,224,226],{"className":225,"code":226,"language":115},[113],"sprintf()"," 对数字进行了格式化处理，可将其转化为对应的字符串。返回的字符串长度不定，根据传入的数字而定。",[104,229,231],{"id":230},"uname-函数和-gname-函数","uname 函数和 gname 函数",[14,233,234,169,237,240,241,169,245,248],{},[41,235,168],{"className":236,"code":168,"language":115},[113],[41,238,173],{"className":239,"code":173,"language":115},[113]," 分别用于将用户id和组id转化为用户名和组名。这里使用了 ",[41,242,244],{"className":243,"code":244,"language":115},[113],"getpwuid()",[41,246,247],{"code":247},"getgrgid()"," 获取对应的用户信息和组信息，如果获取不到则返回该 id 的字符串形式。",[14,250,251,254],{},[41,252,253],{"code":253},"ls"," 的功能基本实现，并支持了一些扩展性比较好的参数，可以通过该程序快速方便地展示文件列表信息。",[256,257,261],"pre",{"className":258,"code":259,"language":115,"meta":260},[113],"#include \u003Cdirent.h>\n#include \u003Cgrp.h>\n#include \u003Clinux\u002Flimits.h>\n#include \u003Cpwd.h>\n#include \u003Cstdio.h>\n#include \u003Cstdlib.h>\n#include \u003Cstring.h>\n#include \u003Csys\u002Fstat.h>\n#include \u003Csys\u002Ftypes.h>\n#include \u003Ctime.h>\n\nvoid list(char dir[]);\nvoid listfile(struct stat* einfo, char fname[]);\nvoid parseParam(char arg[]);\nchar* mod2str(int mod);\nchar* num2str(unsigned int num);\nchar* uname(gid_t uid);\nchar* gname(gid_t gid);\n\nstatic unsigned param = 0;\n#define P_a 0b1\n#define P_l 0b10\n#define P_R 0b100\n#define P_t 0b1000\n#define P_r 0b10000\n#define P_i 0b100000\n#define P_s 0b1000000\n\nint main(int argc, char* argv[]) {\n    int havePath = 0;\n    for (int i = 1; i \u003C argc; i++)\n        if (*argv[i] == '-')\n            parseParam(argv[i]);\n        else\n            havePath++;\n    while (--argc)\n        if (**++argv != '-')\n            list(*argv);\n    if (!havePath)\n        list(\".\");\n    return 0;\n}\n\nvoid list(char dir[]) {\n    \u002F\u002F 打开目录\n    DIR* dirp = opendir(dir);\n    if (!dirp) {\n        closedir(dirp);\n        struct stat einfo;\n        if (stat(dir, &einfo) == -1)\n            fprintf(stderr, \"zls: 无法访问 '%s': 没有那个文件或目录。\\n\", dir);\n        else if (!(param & P_R)) {\n            listfile(&einfo, dir);\n            printf(\"\\n\");\n        }\n        return;\n    }\n    printf(\"%s:\\n\", dir);\n    \u002F\u002F 生成列表\n    int entc, enti[PATH_MAX];\n    struct entbrief {\n        char name[NAME_MAX];\n        time_t mtime;\n    };\n    struct entbrief* entv = malloc(sizeof(struct entbrief[PATH_MAX]));\n    struct dirent* entp;\n    for (entc = 0; (entp = readdir(dirp)) != NULL;) {\n        if (!(param & P_a) && *entp->d_name == '.')\n            continue;\n        struct stat einfo;\n        strcpy(entv[entc].name, entp->d_name);\n        if (param & P_t) {\n            char path[PATH_MAX];\n            sprintf(path, \"%s\u002F%s\", dir, entp->d_name);\n            if (stat(path, &einfo) == -1)\n                continue;\n            entv[entc].mtime = einfo.st_mtim.tv_sec;\n        }\n        enti[entc] = entc;\n        entc++;\n    }\n    \u002F\u002F 排序\n    for (int i = 1, j, temp; i \u003C entc; i++) {\n        temp = enti[i];\n        for (j = i;\n             \u002F\u002F 排序的condition比较长，所以称之为“悲伤的猪大排”\n             j > 0 &&\n             !!(param & P_r) ^\n                 (param & P_t\n                      ? entv[enti[j - 1]].mtime > entv[temp].mtime\n                      : strcmp(entv[enti[j - 1]].name, entv[temp].name) > 0);\n             j--)\n            enti[j] = enti[j - 1];\n        enti[j] = temp;\n    }\n    \u002F\u002F 输出\n    for (int i = 0; i \u003C entc; i++) {\n        struct stat einfo;\n        char path[PATH_MAX];\n        sprintf(path, \"%s\u002F%s\", dir, entv[enti[i]].name);\n        if (stat(path, &einfo) == -1)\n            continue;\n        listfile(&einfo, entv[enti[i]].name);\n        printf(param & P_l || i % 5 == 4 ? \"\\n\" : \"  \\t\");\n    }\n    printf(param & P_l ? \"\\n\" : \"\\n\\n\");\n    closedir(dirp);\n    \u002F\u002F 递归\n    for (int i = 0; i \u003C entc && param & P_R; i++) {\n        if (!strcmp(entv[i].name, \".\") || !strcmp(entv[i].name, \"..\"))\n            continue;\n        char path[PATH_MAX];\n        sprintf(path, \"%s\u002F%s\", dir, entv[i].name);\n        list(path);\n    }\n    free(entv);\n}\n\nvoid listfile(struct stat* einfo, char fname[]) {\n    if (param & P_i)\n        printf(\"%8lu  \", einfo->st_ino);\n    if (param & P_s)\n        \u002F\u002F Zhilu 纠正：应该是4KB向上取整\n        printf(\"%4ld  \",\n               (einfo->st_size \u002F 4096 * 4) + (einfo->st_size % 4096 ? 4 : 0));\n    if (param & P_l) {\n        printf(\"%s  \", mod2str(einfo->st_mode));\n        printf(\"%4d  \", (int)einfo->st_nlink);\n        printf(\"%-8s  \", uname(einfo->st_uid));\n        printf(\"%-8s  \", gname(einfo->st_gid));\n        printf(\"%8ld  \", einfo->st_size);\n        printf(\"%.12s  \", 4 + ctime((const time_t*)&einfo->st_mtim));\n    }\n    printf(S_ISDIR(einfo->st_mode) ? \"\\033[1;34m%-10s\\033[0m\" : \"%-10s\", fname);\n}\n\nvoid parseParam(char arg[]) {\n#define CheckParam(ch)   \\\n    if (*arg == *#ch) {  \\\n        param |= P_##ch; \\\n        continue;        \\\n    }\n    while (*++arg) {\n        CheckParam(a);\n        CheckParam(l);\n        CheckParam(R);\n        CheckParam(t);\n        CheckParam(r);\n        CheckParam(i);\n        CheckParam(s);\n        fprintf(stderr, \"zls: 未知参数 '-%c'。\\n\", *arg);\n    }\n#undef CheckParam\n}\n\nchar* mod2str(int mod) {\n    static char str[] = \"----------\";\n    str[0] = S_ISDIR(mod) ? 'd' : S_ISLNK(mod) ? 'l' : '-';\n    str[1] = mod & S_IRUSR ? 'r' : '-';\n    str[2] = mod & S_IWUSR ? 'w' : '-';\n    str[3] = mod & S_IXUSR ? 'x' : '-';\n    str[4] = mod & S_IRGRP ? 'r' : '-';\n    str[5] = mod & S_IWGRP ? 'w' : '-';\n    str[6] = mod & S_IXGRP ? 'x' : '-';\n    str[7] = mod & S_IROTH ? 'r' : '-';\n    str[8] = mod & S_IWOTH ? 'w' : '-';\n    str[9] = mod & S_IXOTH ? 'x' : '-';\n    return str;\n}\n\nchar* num2str(unsigned int num) {\n    static char str[NAME_MAX];\n    sprintf(str, \"%d\", num);\n    return str;\n}\n\nchar* uname(gid_t uid) {\n    struct passwd* pwp = getpwuid(uid);\n    return pwp ? pwp->pw_name : num2str(uid);\n}\n\nchar* gname(gid_t gid) {\n    struct group* grp = getgrgid(gid);\n    return grp ? grp->gr_name : num2str(gid);\n}\n","",[41,262,259],{"__ignoreMap":260},{"title":260,"searchDepth":264,"depth":264,"links":265},4,[266,268],{"id":12,"depth":267,"text":12},2,{"id":102,"depth":267,"text":102,"children":269},[270,272,273,274,275,276],{"id":106,"depth":271,"text":107},3,{"id":140,"depth":271,"text":141},{"id":177,"depth":271,"text":178},{"id":200,"depth":271,"text":201},{"id":214,"depth":271,"text":215},{"id":230,"depth":271,"text":231},[278],"开发","2023-03-18 22:20:20","这个程序是一个基于C语言的命令行工具，用于在终端中展示指定目录下的文件信息。它的名字是“zls”。",false,"md",null,{"slots":285},{},true,"\u002F2023\u002Fzls",{"text":289,"minutes":290,"time":291,"words":292},"9 min read",8.635,518100,1727,{"title":5,"description":280},{"loc":287},"posts\u002F2023\u002Fzls",[297,298,299],"C语言","Linux命令","命令实现","tech","2023-11-30 22:39:04","BpuAgschYiQp9TI1kxR3hTdhR7YumkAjIHhGqU7hk58",[304,309],{"title":305,"path":306,"stem":307,"date":308,"type":300,"children":-1},"Linux QQ 崩溃解决办法","\u002F2023\u002Flinuxqq-crash","posts\u002F2023\u002Flinuxqq-crash","2023-03-16 21:02:22",{"title":310,"path":311,"stem":312,"date":313,"type":300,"children":-1},"VS Code 简单配置项","\u002F2023\u002Fvscode-simple-config","posts\u002F2023\u002Fvscode-simple-config","2023-04-13 20:05:50",1782091376756]