Linux中读取目录: opendir,fdopendir,readdir,rewinddir,closedir,telldir,seekdir

这篇博客介绍了Linux中读取目录的相关函数,如opendir和fdopendir,以及dirent结构体。文章阐述了目录的访问权限和其在不同Unix系统中的实现,并通过示例代码展示了如何使用这些函数遍历文件系统,同时提到了在处理符号链接时的注意事项。

Directories can be read by anyone who has access permission to read the directory. But only the kernel can write to a directory, to preserve file system sanity(心智健康)(只有内核可以写如目录来保证文件系统的正常运作).

目录的写权限和执行权限可以让我们在目录中创建新文件和移除文件。但是并不能让我们写入目录它本身。

目录的实际形式取决于Unix系统实现和文件系统的设计。早期的系统中,使用简单的结构:目录entry有16字节,14byte for filename, 2byte for inode number。随着更长的文件名加入,每个entry都是可变的长度。

为简化读目录操作,POSIX1规定了一系列directory routines。许多实现访问应用程序使用read来访问目录的内容。

opendir, fdopendir - open a directory

#include <sys/types.h>
#include <dirent.h>

DIR *opendir(const char *name);
DIR *fdopendir(int fd);
//Return: pointer if OK, NULL on error
<think>我们使用opendir,readdir,closedir来遍历目录,同时使用lstat获取每个文件的详细信息,然后格式化输出,实现ls-l的功能。ls-l的典型输出格式包括:文件权限、链接数、所有者、所属组、文件大小、最后修改时间、文件名。步骤:1.使用opendir打开目录。2.使用readdir读取目录中的每一个条目。3.对于每个条目(除了"."和".."),使用lstat获取文件属性(注意:我们使用lstat而不是stat,因为lstat不会跟随符号链接)。4.从stat结构体中提取所需信息,并格式化为ls-l的样式。-文件类型和权限:使用st_mode字段,可以通过宏(如S_ISDIR)判断文件类型,然后使用位掩码解析权限。-硬链接数:st_nlink-所有者:st_uid->需要转换为用户名(可以使用getpwuid)-所属组:st_gid->需要转换为组名(可以使用getgrgid)-文件大小:st_size-最后修改时间:st_mtime->转换为字符串(可以使用strftime)-文件名:d_name5.注意:对于特殊文件(如符号链接),可能需要额外显示链接指向的目标(使用readlink)。注意:在实现中,我们可能需要跳过"."和".."目录,以避免死循环(在递归遍历目录时很重要,但这里只是当前目录)。由于涉及用户名和组名的转换,我们需要包含<pwd.h>和<grp.h>。关于时间格式,通常ls-l显示的时间格式为“月日时间”(如果文件修改时间在6个月之内)或者“月日年”(6个月之外)。我们将编写一个函数来打印单个文件的详细信息。另外,我们还需要考虑输出对齐(比如使用printf的宽度控制)。示例代码结构:1.打开目录,循环读取条目。2.对每个条目调用lstat。3.打印文件信息。我们首先不考虑递归(即只处理当前目录),类似ls-l。注意:符号链接的处理:我们使用lstat获取的是符号链接本身的信息,而stat会获取指向文件的信息。因此,在显示符号链接时,我们还需要显示其指向的目标(通过readlink)。但是,在标准的ls-l输出中,符号链接的格式为:"lrwxrwxrwx1rootroot7Apr222020lib->usr/lib"因此,对于符号链接,我们在文件名后面额外打印"->"和链接目标。步骤:1.打开目录opendir)2.读取条目(readdir),跳过"."和".."3.为每个条目拼接完整路径(如果传入的是目录,则我们需要进入该目录?但是我们的函数只处理当前目录下的文件。因此,我们不需要拼接路径来获取文件属性?不对,因为当前目录下的文件,在stat函数中需要路径。如果传入的路径是目录,那么目录下的文件需要加上目录前缀才能正确获取stat。所以,我们需要拼接目录名和文件名?)但是,用户执行我们的程序时,可能这样调用:./myls.或者./myls/etc,所以我们需要根据传入的路径来处理。在main函数中,我们打开传入的目录(第一个参数),然后遍历该目录下的所有文件,但是获取文件属性时,我们需要提供文件的完整路径(或者相对于当前目录的路径)。因此,我们需要将目录路径和文件名拼接起来。例如:如果传入的是/etc,那么要获取passwd文件的信息,我们需要拼接成"/etc/passwd"。注意:如果传入的是当前目录".",那么文件名为"test.txt",则拼接为"./test.txt"?但是这样会有多余的"./",实际上,我们可以直接使用"test.txt"来stat。但是,为了通用性(特别是目录不是当前目录的情况),我们使用路径拼接。因此,我们使用:charfilepath[PATH_MAX];snprintf(filepath,sizeof(filepath),"%s/%s",dirname,dp->d_name);然后对filepath调用lstat。但是,当dirname是根目录"/"时,拼接后就是"/filename",正确。步骤:1.验证参数个数,如果没有参数,默认使用当前目录(".")。2.打开目录。3.遍历目录项,跳过"."和".."4.拼接路径,获取lstat信息。5.打印文件信息。格式化输出函数(print_stat):输入:文件路径(用于获取链接目标)和structstat结构体,以及文件名(不包含路径)我们将实现一个函数,将文件模式(st_mode)转换为字符串,如"-rwxr-xr-x"。文件类型转换:文件类型:S_ISLNK(st_mode)->'l'S_ISDIR(st_mode)->'d'S_ISCHR(st_mode)->'c'S_ISBLK(st_mode)->'b'S_ISFIFO(st_mode)->'p'S_ISSOCK(st_mode)->'s'否则为普通文件'-'权限:三组(所有者、组、其他),每组三位(读、写、执行)。我们可以用一个数组来表示权限,然后按位检查。具体实现:用一个字符数组perms[10],然后依次设置:perms[0]=文件类型字符(如上)perms[1]=(st_mode&S_IRUSR)?'r':'-';perms[2]=(st_mode&S_IWUSR)?'w':'-';perms[3]=(st_mode&S_IXUSR)?'x':'-';...以此类推然后,用户名和组名通过getpwuid和getgrgid获得。时间格式化:使用strftime,但注意,ls的输出有两种格式。我们可以这样判断:time_tnow=time(NULL);doublediff=difftime(now,st.st_mtime);//秒数差//如果超过6个月(按180天算),则显示年份,否则显示时间但是注意:ls-l默认显示的是修改时间。例子:我们使用两个字符数组,一个用于近期的格式,一个用于远期的格式。符号链接的特殊处理:调用readlink获取链接目标,然后打印"文件名->目标"。注意:readlink需要目标缓冲区,并且不会添加结束符,需要我们手动添加。开始写代码,注意错误处理。由于代码较长,我们逐步构建。注意:获取用户名和组名可能失败,需要处理(如果失败则输出uid/gid的数值)。我们首先包含必要的头文件。</think> ### 使用 opendir/readdir/closedir 和 lstat 实现 ls -l 功能 #### 实现思路 1. **流程框架** ```mermaid graph TD A[启动程序] --> B{参数检查} B --> |参数错误| C[打印用法并退出] B --> |路径有效| D[opendir 打开目录] D --> E{读取目录项?} E --> |是| F[readdir 获取目录项] F --> G{是 . 或 .. ?} G --> |是| E G --> |否| H[lstat 获取文件信息] H --> I[解析文件属性] I --> J[格式化为 ls -l 输出] J --> E E --> |否| K[closedir 关闭目录] K --> L[程序结束] ``` 2. **核心函数说明** - `opendir()`:打开指定目录流 - `readdir()`:读取目录项(返回 `dirent` 结构) - `lstat()`:获取文件信息(填充 `stat` 结构) - `closedir()`:关闭目录流 #### 完整实现代码 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <dirent.h> #include <sys/stat.h> #include <sys/types.h> #include <pwd.h> #include <grp.h> #include <time.h> // 将文件模式转换为字符串表示 void mode_to_str(mode_t mode, char *str) { // 文件类型 str[0] = S_ISDIR(mode) ? 'd' : S_ISLNK(mode) ? 'l' : S_ISCHR(mode) ? 'c' : S_ISBLK(mode) ? 'b' : S_ISFIFO(mode) ? 'p' : S_ISSOCK(mode) ? 's' : '-'; // 权限位 str[1] = (mode & S_IRUSR) ? 'r' : '-'; str[2] = (mode & S_IWUSR) ? 'w' : '-'; str[3] = (mode & S_IXUSR) ? 'x' : '-'; str[4] = (mode & S_IRGRP) ? 'r' : '-'; str[5] = (mode & S_IWGRP) ? 'w' : '-'; str[6] = (mode & S_IXGRP) ? 'x' : '-'; str[7] = (mode & S_IROTH) ? 'r' : '-'; str[8] = (mode & S_IWOTH) ? 'w' : '-'; str[9] = (mode & S_IXOTH) ? 'x' : '-'; str[10] = '\0'; } int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "用法: %s <目录路径>\n", argv[0]); exit(EXIT_FAILURE); } DIR *dir = opendir(argv[1]); if (!dir) { perror("opendir"); exit(EXIT_FAILURE); } struct dirent *entry; while ((entry = readdir(dir)) != NULL) { // 跳过 . 和 .. if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; char fullpath[PATH_MAX]; snprintf(fullpath, sizeof(fullpath), "%s/%s", argv[1], entry->d_name); struct stat st; if (lstat(fullpath, &st) == -1) { // 使用lstat不追踪符号链接 perror("lstat"); continue; } // 1. 文件权限和类型 char modestr[11]; mode_to_str(st.st_mode, modestr); // 2. 链接数 nlink_t links = st.st_nlink; // 3. 所有者 struct passwd *pw = getpwuid(st.st_uid); char *owner = pw ? pw->pw_name : "unknown"; // 4. 所属组 struct group *gr = getgrgid(st.st_gid); char *group = gr ? gr->gr_name : "unknown"; // 5. 文件大小 off_t size = st.st_size; // 6. 修改时间 char timebuf[80]; struct tm *tm = localtime(&st.st_mtime); strftime(timebuf, sizeof(timebuf), "%b %d %H:%M", tm); // 7. 输出格式化信息 printf("%s %2lu %-8s %-8s %8ld %s %s", modestr, (unsigned long)links, owner, group, (long)size, timebuf, entry->d_name); // 如果是符号链接,显示目标 if (S_ISLNK(st.st_mode)) { char linkbuf[PATH_MAX]; ssize_t len = readlink(fullpath, linkbuf, sizeof(linkbuf) - 1); if (len != -1) { linkbuf[len] = '\0'; printf(" -> %s", linkbuf); } } printf("\n"); } closedir(dir); return 0; } ``` #### 关键函数解析 1. **`lstat()` vs `stat()`** - 使用 `lstat()` 避免符号链接跟踪 - 确保显示链接文件本身的属性而非目标文件[^2] ```c lstat("/path/to/file", &st); // 获取符号链接本身的信息 ``` 2. **文件类型检测** ```c if (S_ISDIR(st.st_mode)) // 目录 if (S_ISREG(st.st_mode)) // 普通文件 if (S_ISLNK(st.st_mode)) // 符号链接 ``` 3. **属性转换函数** - `getpwuid()`:UID → 用户名 - `getgrgid()`:GID → 组名 - `strftime()`:时间戳 → 可读格式 4. **符号链接处理** ```c readlink("/path/to/symlink", buffer, size); // 读取链接目标 ``` #### 编译与执行 ```bash # 编译 gcc myls.c -o myls # 执行(对比系统命令) ./myls . # 自制命令 ls -l # 系统命令 ``` #### 输出格式示例 ``` drwxr-xr-x 2 user group 4096 Apr 15 10:30 Documents -rw-r--r-- 1 user group 1024 Apr 14 14:25 report.txt lrwxrwxrwx 1 user group 11 Apr 10 09:15 link -> target/file ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猎羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值