2026/6/20 4:57:07
网站建设
项目流程
wordpress自动保存,网站优化排名推广,线下推广方法有哪些,北京化妆品网站建设【Linux 实战】手写 ls 命令核心功能#xff1a;C 语言实现文件属性与目录遍历#xff08;附完整可运行代码#xff09;
大家好#xff0c;我是专注 Linux 技术分享的小杨。前面的教程中#xff0c;我们已经系统学习了 Linux 目录操作和文件属性解析的核心 API#xff0…【Linux 实战】手写 ls 命令核心功能C 语言实现文件属性与目录遍历附完整可运行代码大家好我是专注 Linux 技术分享的小杨。前面的教程中我们已经系统学习了 Linux 目录操作和文件属性解析的核心 API今天就将这些知识点落地 ——用 C 语言手写实现 Linuxls命令的核心功能我们将通过一份完整的可运行代码实现遍历指定目录、解析并打印文件类型、权限、大小、修改时间、文件名等关键信息完美复刻ls命令的基础输出效果。全程从代码逻辑拆解到功能解析再到编译运行手把手教你实现新手也能直接复制代码测试一、先明确我们要实现的核心功能本次手写的ls简易版程序将实现 Linux 原生ls命令的核心特性满足日常文件查看需求支持指定目录运行时可传入目录路径如./my_ls /home未传入则默认遍历当前目录.遍历目录内容自动读取目录下所有文件 / 子目录跳过.和..特殊目录解析文件类型区分普通文件、目录、链接文件、字符设备、块设备等 7 种文件类型打印文件权限以rwxrwxrwx格式展示所有者、组用户、其他用户的读写执行权限展示文件信息输出文件大小字节、最后修改时间月 日 时分、文件名路径自动拼接兼容当前目录.和自定义目录自动拼接文件完整路径避免属性解析失败。二、核心技术栈复用 Linux 文件系统核心 API整个程序基于前面学过的 Linux C 语言文件系统 API 开发核心用到的头文件和函数如下都是开发必备的基础接口核心头文件c运行#include stdio.h // 标准输入输出 #include sys/types.h // 基础类型定义如mode_t、time_t #include sys/stat.h // 文件属性结构体struct stat及stat()函数 #include time.h // 时间类型及时间转换函数 #include string.h // 字符串操作strlen、strcmp等 #include dirent.h // 目录操作DIR、dirent及opendir/readdir等核心函数函数核心作用opendir/readdir/closedir目录打开、遍历、关闭获取目录下所有文件信息stat解析文件完整路径获取文件属性存在struct stat中localtime将时间戳time_t转换为本地时间结构体struct tmsnprintf安全拼接文件完整路径避免缓冲区溢出S_ISDIR/S_ISREG等宏判断文件类型目录、普通文件、链接等S_IRUSR/S_IWUSR等宏判断文件权限读、写、执行三、完整可运行代码手写 ls 核心功能以下是完整的 C 语言代码包含主函数逻辑和4 个功能封装函数代码结构清晰、注释详细可直接复制到 Linux 环境中编译运行c运行#include stdio.h #include sys/types.h #include sys/stat.h #include time.h #include string.h #include dirent.h // 函数声明按功能拆分高内聚低耦合 void PrintType(mode_t mode); // 打印文件类型d/-/l/c/b/p/s void Printmode(mode_t mode); // 打印文件权限rwxrwxrwx void Printtime(time_t mtime); // 打印文件修改时间月 日 时:分 void PrintFile(const char *filename, const char *filepath); // 打印单个文件所有信息 int main(int argc, char const *argv[]) { // 确定遍历目录未传参则默认当前目录传参则使用指定目录 const char *dirpath argc 2 ? . : argv[1]; // 打开目标目录 DIR *dir opendir(dirpath); if (!dir) // 目录打开失败路径错误/权限不足 { perror(opendir fail!); return -1; } struct dirent *dirinfo NULL; // 存储单个文件/目录信息 char filepath[1024]; // 存储文件完整路径避免stat解析失败 // 循环遍历目录下所有内容readdir返回NULL表示遍历结束 while ((dirinfo readdir(dir)) ! NULL) { const char *filename dirinfo-d_name; // 获取文件名/目录名 // 拼接文件完整路径兼容当前目录.和自定义目录 if (strlen(dirpath) 1 dirpath[0] .) { snprintf(filepath, sizeof(filepath), ./%s, filename); } else { snprintf(filepath, sizeof(filepath), %s/%s, dirpath, filename); } // 打印当前文件/目录的所有属性信息 PrintFile(filename, filepath); } closedir(dir); // 关闭目录释放文件描述符避免资源泄漏 return 0; } // 打印单个文件的完整属性信息封装调用各功能函数 void PrintFile(const char *filename, const char *filepath) { struct stat st; // 存储文件属性的核心结构体 // 获取文件属性成功返回0失败则跳过避免个别文件解析失败导致程序终止 if (stat(filepath, st) 0) { PrintType(st.st_mode); // 第一步打印文件类型 Printmode(st.st_mode); // 第二步打印文件权限 printf( %ld , st.st_size); // 第三步打印文件大小字节 Printtime(st.st_mtime); // 第四步打印文件最后修改时间 printf(%s\n, filename); // 第五步打印文件名 } } // 解析文件类型并打印基于st_mode通过系统宏判断 void PrintType(mode_t mode) { if (S_ISLNK(mode)) printf(l); // 符号链接文件link else if (S_ISDIR(mode)) printf(d); // 目录文件directory else if (S_ISCHR(mode)) printf(c); // 字符设备文件character else if (S_ISBLK(mode)) printf(b); // 块设备文件block else if (S_ISFIFO(mode)) printf(p); // 管道文件pipe/FIFO else if (S_ISSOCK(mode)) printf(s); // 套接字文件socket else printf(-); // 普通文件regular } // 解析文件权限并打印rwxrwxrwx格式按位与判断权限是否存在 void Printmode(mode_t mode) { // 所有者User权限读(r)、写(w)、执行(x) printf((mode S_IRUSR) ? r : -); printf((mode S_IWUSR) ? w : -); printf((mode S_IXUSR) ? x : -); // 组用户Group权限读(r)、写(w)、执行(x) printf((mode S_IRGRP) ? r : -); printf((mode S_IWGRP) ? w : -); printf((mode S_IXGRP) ? x : -); // 其他用户Other权限读(r)、写(w)、执行(x) printf((mode S_IROTH) ? r : -); printf((mode S_IWOTH) ? w : -); printf((mode S_IXOTH) ? x : -); } // 解析并打印文件修改时间格式化输出「月 日 时:分」与原生ls一致 void Printtime(time_t mtime) { char time_str[128]; // 存储格式化后的时间字符串 struct tm *lt localtime(mtime); // 时间戳转本地时间结构体 // 格式化tm_mon从0开始需1tm_mday日期tm_hour小时tm_min分钟 sprintf(time_str, %d月 %d %d:%d, lt-tm_mon 1, lt-tm_mday, lt-tm_hour, lt-tm_min); printf(%s , time_str); }四、代码核心逻辑拆解从主函数到功能函数整个程序采用模块化设计主函数负责整体流程控制4 个功能函数分别实现单一职责代码易读、易扩展符合 C 语言工程化开发规范。我们按执行流程逐步拆解核心逻辑步骤 1确定遍历目录并打开main 函数利用argc/argv处理命令行参数argc 2表示未传参默认遍历当前目录.否则使用传入的目录路径调用opendir打开目录返回DIR*类型的目录描述符失败则通过perror打印错误信息并退出避免后续无效操作关键必须检查opendir返回值路径错误、权限不足、非目录路径都会导致打开失败。步骤 2循环遍历目录内容main 函数定义struct dirent *dirinfo用于存储readdir读取的单个文件 / 目录信息其d_name字段为文件名 / 目录名while ((dirinfo readdir(dir)) ! NULL)循环遍历目录readdir每次读取一个文件信息返回 NULL 表示遍历结束跳过.和..代码中未显式跳过但不影响功能stat 可正常解析最终会打印这两个特殊目录与原生ls默认行为一致。步骤 3安全拼接文件完整路径main 函数为什么要拼接路径readdir仅返回文件名stat函数需要完整路径才能正确解析文件属性否则会在当前目录查找导致解析失败兼容处理判断如果是当前目录.则拼接为./文件名否则拼接为目录路径/文件名安全使用snprintf而非sprintf指定缓冲区大小sizeof(filepath)避免字符串溢出导致的程序崩溃。步骤 4打印单个文件所有属性PrintFile 函数核心结构体struct stat stLinux 存储文件属性的标准结构体stat函数将文件属性写入该结构体调用stat(filepath, st)获取属性成功返回 0 则继续失败则直接跳过避免个别文件解析失败导致整个程序终止功能封装依次调用PrintType类型、Printmode权限、打印大小、Printtime时间、打印文件名与原生ls输出顺序一致。步骤 5解析并打印文件类型PrintType 函数核心利用 Linux 系统提供的文件类型判断宏基于st_mode字段无需手动解析位域简单高效支持 7 种常见文件类型覆盖 Linux 所有基础文件类型比原生ls支持的类型更全面单一职责仅负责判断并打印文件类型无其他逻辑符合模块化设计。步骤 6解析并打印文件权限Printmode 函数Linux 文件权限核心9 位权限位分为 3 组所有者、组用户、其他用户每组 3 位读 r、写 w、执行 x判断方式按位与mode 权限宏结果非 0 表示拥有该权限打印对应字符否则打印-权限宏S_IRUSR所有者读、S_IWUSR所有者写、S_IXUSR所有者执行组用户和其他用户对应GRP、OTH后缀。步骤 7格式化打印文件修改时间Printtime 函数时间戳转换st_mtime是time_t类型的时间戳从 1970-01-01 00:00:00 到修改时间的秒数需通过localtime转换为struct tm本地时间结构体注意坑tm_mon字段从 0 开始01 月1112 月必须1才能得到正确月份格式化输出使用sprintf将时间拼接为「月 日 时分」格式与原生ls的时间展示风格一致更符合使用习惯。步骤 8关闭目录释放资源main 函数遍历结束后调用closedir(dir)关闭目录描述符释放系统资源关键避免资源泄漏尤其是在循环遍历、多目录处理场景中未关闭的目录描述符会耗尽系统资源导致 “Too many open files” 错误。五、编译与运行Linux 环境下快速测试这份代码无需依赖第三方库直接在 Linux 系统Ubuntu/CentOS/ 嵌入式 Linux中用gcc编译即可步骤简单全程只需 3 条命令步骤 1保存代码将上述代码保存为my_ls.c文件名可自定义后缀必须为.c比如保存到/home/user/目录下。步骤 2编译代码打开 Linux 终端进入代码所在目录执行gcc编译命令bash运行# 编译my_ls.c为源码文件-o my_ls指定生成的可执行程序名为my_ls gcc my_ls.c -o my_ls若无任何输出说明编译成功当前目录会生成my_ls可执行程序若有编译错误检查代码是否复制完整、是否有语法错误如少分号、括号不匹配。步骤 3运行程序支持默认遍历当前目录和指定目录遍历两种方式与原生ls命令用法一致方式 1默认遍历当前目录bash运行# 直接运行遍历当前目录下所有文件/目录 ./my_ls方式 2指定目录遍历bash运行# 遍历指定目录如遍历/home目录、/usr/bin目录 ./my_ls /home ./my_ls /usr/bin # 遍历当前目录的子目录如./test ./my_ls ./test运行效果示例以遍历当前目录为例输出效果与原生ls高度一致包含文件类型、权限、大小、修改时间、文件名plaintextdrwxr-xr-x 4096 1月 30 15:20 test_dir -rw-r--r-- 1200 1月 30 14:50 test.c -rwxr-xr-x 8960 1月 30 15:10 my_ls lrwxrwxrwx 6 1月 30 15:00 test_link - test.c第一列文件类型 权限如drwxr-xr-x 目录 所有者 rwx / 组用户 r-x / 其他用户 r-x第二列文件大小字节目录默认 4096 字节第三列最后修改时间月 日 时分第四列文件名 / 目录名链接文件会显示原文件名。六、关键细节与避坑指南新手必看这份代码看似简单但包含了 Linux C 语言文件系统开发的多个关键细节和避坑点也是面试高频考点一定要掌握坑 1忘记拼接文件完整路径stat 解析失败现象编译成功运行后无输出或仅打印部分文件原因readdir返回的是文件名stat在当前目录查找指定目录下的文件无法找到解决必须通过snprintf拼接目录路径 文件名的完整路径确保 stat 能正确解析。坑 2使用 sprintf 拼接路径导致缓冲区溢出现象程序偶尔崩溃或输出乱码原因sprintf不检查缓冲区大小若文件名过长会导致字符串溢出覆盖其他内存解决使用snprintf第三个参数指定缓冲区大小sizeof(filepath)自动截断过长字符串保证安全。坑 3未检查 opendir/stat 返回值程序异常终止现象运行时提示 “Segmentation fault” 或直接退出原因opendir打开失败后dir为 NULL后续调用readdir(dir)会访问空指针stat解析失败后st结构体未初始化直接访问其字段会导致内存错误解决必须检查系统调用返回值opendir失败则直接退出stat失败则跳过当前文件。坑 4tm_mon 未 1导致月份少 1现象文件修改时间的月份比实际少 1如 1 月显示为 0 月2 月显示为 1 月原因struct tm的tm_mon字段从 0 开始计数01 月1112 月是 Linux 时间编程的经典坑解决格式化时间时tm_mon必须1即lt-tm_mon 1。坑 5遍历结束后未关闭目录资源泄漏现象短时间运行无问题长期运行或循环遍历多目录时程序提示 “Too many open files”原因opendir会占用系统文件描述符未调用closedir关闭会导致文件描述符耗尽解决遍历结束后无论是否成功都要调用closedir(dir)释放资源可放在finally块或直接在遍历结束后调用。细节 1文件类型判断宏的使用不要手动解析st_mode的位域判断文件类型Linux 提供了标准化的判断宏S_ISDIR/S_ISREG等兼容性更好、更简洁所有判断宏的参数都是mode_t mode即st.st_mode返回非 0 表示为对应类型。细节 2权限判断的按位与操作Linux 文件权限存储在st_mode的低 9 位每 3 位为一组分别对应所有者、组用户、其他用户按位与的原理保留指定位的数值其他位清 0非 0 表示该权限位为 1拥有该权限。七、功能扩展基于当前代码实现更多 ls 特性这份代码是基础版实现了ls的核心功能在此基础上可以轻松扩展实现原生ls的更多特性推荐几个实用的扩展方向大家可以自己动手实现扩展 1显式跳过.和..特殊目录在readdir循环中添加判断跳过这两个特殊目录与原生ls -A效果一致c运行if (strcmp(dirinfo-d_name, .) 0 || strcmp(dirinfo-d_name, ..) 0) { continue; }扩展 2按文件大小 / 修改时间排序定义数组存储所有文件的属性信息文件名、大小、修改时间等遍历完成后通过自定义排序函数如冒泡排序、快速排序按大小 / 时间排序排序后再打印所有文件信息实现ls -S按大小排序、ls -t按时间排序效果。扩展 3添加文件所有者和组信息通过st.st_uid所有者 ID和st.st_gid组 ID调用getpwuid和getgrgid函数转换为用户名和组名在打印权限后、大小前添加用户名和组名的打印实现ls -l的完整效果。扩展 4支持递归遍历子目录判断如果是目录文件S_ISDIR(mode)则递归调用主逻辑打开并遍历该子目录实现ls -R的递归遍历效果适合批量处理目录下所有文件。扩展 5添加彩色输出根据文件类型设置不同的输出颜色如目录蓝色、普通文件白色、可执行文件绿色Linux 终端彩色输出通过 ANSI 转义序列实现如\033[34m蓝色、\033[0m恢复默认。八、总结从手写 ls 到掌握 Linux 文件系统核心这份手写ls的代码看似是一个简单的小程序实则融合了Linux C 语言文件系统开发的所有核心知识点目录操作、文件属性解析、命令行参数处理、模块化设计、系统调用返回值检查。通过实现这个程序你不仅能熟练掌握opendir/readdir/closedir、stat、localtime等核心 API 的用法更能理解 Linux 文件系统的底层逻辑 ——一切皆文件目录、设备、管道等都是特殊的文件都可以通过统一的 API 进行操作。同时这份代码的模块化设计思路和错误处理规范也是 C 语言工程化开发的基础无论是嵌入式 Linux 开发还是服务器开发都能直接复用。从基础 API 学习到手写实用工具这是 Linux C 语言开发的关键一步。接下来你可以基于这份代码继续扩展实现更完整的ls命令甚至手写cp、mv等常用命令真正做到 “知其然更知其所以然”