news 2026/5/4 19:06:19

【Linux 实战】手写 ls 命令核心功能:C 语言实现文件属性与目录遍历(附完整可运行代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Linux 实战】手写 ls 命令核心功能:C 语言实现文件属性与目录遍历(附完整可运行代码)

【Linux 实战】手写 ls 命令核心功能:C 语言实现文件属性与目录遍历(附完整可运行代码)

大家好,我是专注 Linux 技术分享的小杨。前面的教程中,我们已经系统学习了 Linux 目录操作和文件属性解析的核心 API,今天就将这些知识点落地 ——用 C 语言手写实现 Linuxls命令的核心功能

我们将通过一份完整的可运行代码,实现遍历指定目录、解析并打印文件类型、权限、大小、修改时间、文件名等关键信息,完美复刻ls命令的基础输出效果。全程从代码逻辑拆解到功能解析,再到编译运行,手把手教你实现,新手也能直接复制代码测试!

一、先明确:我们要实现的核心功能

本次手写的ls简易版程序,将实现 Linux 原生ls命令的核心特性,满足日常文件查看需求:

  1. 支持指定目录:运行时可传入目录路径(如./my_ls /home),未传入则默认遍历当前目录(.);
  2. 遍历目录内容:自动读取目录下所有文件 / 子目录,跳过...特殊目录;
  3. 解析文件类型:区分普通文件、目录、链接文件、字符设备、块设备等 7 种文件类型;
  4. 打印文件权限:以rwxrwxrwx格式展示所有者、组用户、其他用户的读写执行权限;
  5. 展示文件信息:输出文件大小(字节)、最后修改时间(月 日 时:分)、文件名;
  6. 路径自动拼接:兼容当前目录(.)和自定义目录,自动拼接文件完整路径,避免属性解析失败。

二、核心技术栈:复用 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 tm)
snprintf安全拼接文件完整路径,避免缓冲区溢出
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开始,需+1;tm_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 st:Linux 存储文件属性的标准结构体,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(所有者执行),组用户和其他用户对应GRPOTH后缀。

步骤 7:格式化打印文件修改时间(Printtime 函数)

  • 时间戳转换:st_mtimetime_t类型的时间戳(从 1970-01-01 00:00:00 到修改时间的秒数),需通过localtime转换为struct tm本地时间结构体;
  • 注意坑:tm_mon字段从 0 开始(0=1 月,11=12 月),必须+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高度一致,包含文件类型、权限、大小、修改时间、文件名:

plaintext

drwxr-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失败则跳过当前文件。

坑 4:tm_mon 未 + 1,导致月份少 1

  • 现象:文件修改时间的月份比实际少 1(如 1 月显示为 0 月,2 月显示为 1 月);
  • 原因:struct tmtm_mon字段从 0 开始计数(0=1 月,11=12 月),是 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),调用getpwuidgetgrgid函数转换为用户名和组名;
  • 在打印权限后、大小前,添加用户名和组名的打印,实现ls -l的完整效果。

扩展 4:支持递归遍历子目录

  • 判断如果是目录文件(S_ISDIR(mode)),则递归调用主逻辑,打开并遍历该子目录;
  • 实现ls -R的递归遍历效果,适合批量处理目录下所有文件。

扩展 5:添加彩色输出

  • 根据文件类型设置不同的输出颜色(如目录蓝色、普通文件白色、可执行文件绿色);
  • Linux 终端彩色输出通过 ANSI 转义序列实现,如\033[34m(蓝色)、\033[0m(恢复默认)。

八、总结:从手写 ls 到掌握 Linux 文件系统核心

这份手写ls的代码,看似是一个简单的小程序,实则融合了Linux C 语言文件系统开发的所有核心知识点:目录操作、文件属性解析、命令行参数处理、模块化设计、系统调用返回值检查。

通过实现这个程序,你不仅能熟练掌握opendir/readdir/closedirstatlocaltime等核心 API 的用法,更能理解 Linux 文件系统的底层逻辑 ——一切皆文件,目录、设备、管道等都是特殊的文件,都可以通过统一的 API 进行操作。

同时,这份代码的模块化设计思路错误处理规范,也是 C 语言工程化开发的基础,无论是嵌入式 Linux 开发还是服务器开发,都能直接复用。

从基础 API 学习到手写实用工具,这是 Linux C 语言开发的关键一步。接下来,你可以基于这份代码继续扩展,实现更完整的ls命令,甚至手写cpmv等常用命令,真正做到 “知其然,更知其所以然”!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/4 12:30:39

AI写论文新选择!这4款AI论文生成神器,解决写论文的燃眉之急!

还在为论文写作烦恼吗&#xff1f;面对大量的文献资料、繁杂的格式要求和频繁的修改&#xff0c;很多学术人士都感觉效率低下&#xff0c;倍感压力&#xff01;别担心&#xff0c;本文将推荐4款实测过的AI论文写作工具&#xff0c;帮助你从文献检索、论文大纲到语言润色&#x…

作者头像 李华
网站建设 2026/5/4 12:29:17

AI写论文必备!这4款AI论文写作工具,让你写职称论文不再愁!

四款实用AI论文写作工具推荐 在撰写期刊论文、毕业论文或职称论文的过程中&#xff0c;不少人可能感到无比烦恼。面对海量的参考文献&#xff0c;仿佛是在大海里捞针&#xff0c;文献的查找让人头疼不已。各种复杂的格式要求也让人倍感压力&#xff0c;反复修改的过程更是消耗…

作者头像 李华
网站建设 2026/5/1 2:36:04

计算机毕设java的校园一卡通管理系统的设计与实现 基于Java技术的高校一卡通管理系统开发与应用 Java环境下校园一卡通综合管理平台的设计与构建

计算机毕设java的校园一卡通管理系统的设计与实现8x2vv9 &#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。随着信息技术的飞速发展&#xff0c;校园管理逐渐向智能化、便捷化方向迈…

作者头像 李华
网站建设 2026/5/4 14:55:57

AI写论文利器登场!4款AI论文生成软件,让你的学术创作快人一步!

在撰写期刊论文、毕业论文或职称论文的过程中&#xff0c;学术人员常常面临不少挑战。手动编写论文时&#xff0c;海量的文献资料仿佛像大海中的针&#xff0c;寻找相关的信息极为困难&#xff1b;格式要求的复杂程度也让人感到无从下手&#xff0c;令许多人忙得不可开交。而在…

作者头像 李华
网站建设 2026/5/1 10:12:40

Apollo 占据网格感知模型:从训练到部署的完整指南

Apollo 占据网格感知模型:从训练到部署的完整指南 引言:什么是占据网格? 第一部分:为什么需要这份指南?(背景与初衷) 第二部分:核心概念与准备工作 2.1 关键术语 2.2 准备工作:驱动确认 第三部分:训练阶段详解 步骤 1: 获取训练环境与数据 步骤 2: 创建并运行容器 步…

作者头像 李华