当前位置: 首页 > news >正文

Linux C 目录流基本操作

简介

目录流是一个抽象的概念,用于表示对目录的访问状态。它类似于文件指针,但专门用于目录操作。目录流通过一个指向 DIR 结构的指针来表示,该指针由 opendir() 函数返回。从OS层面理解过程如下所示:

  • 打开目录文件:内核会查找目录的路径,找到对应的 inode,并打开这个目录文件。

  • 创建目录流结构:内核会为目录流分配一个内部结构(如 DIR 结构),并初始化一些状态信息,例如当前读取位置、目录文件的描述符等。

  • 返回目录流指针:将目录流结构的指针返回给用户程序。

打开和关闭目录 

opendir() 函数

opendir() 函数用于打开一个目录,并返回一个目录流指针。其原型如下:

#include <sys/types.h>
#include <dirent.h>DIR *opendir(const char *name);
  • 参数 name 是目录的路径。

  • 返回值是一个指向 DIR 结构的指针。如果目录成功打开,返回非空指针;如果失败,返回 NULL

其中DIR结构体如下:

struct __dirstream {void *__fd;           // 文件描述符,指向目录文件char *__data;         // 缓存区,存储从目录文件读取的数据int __entry_data;     // 缓存区中有效数据的条目数char *__ptr;          // 当前读取位置的指针int __entry_ptr;      // 当前读取位置的条目索引size_t __allocation;  // 缓存区分配的大小size_t __size;        // 缓存区中实际数据的大小__libc_lock_define(, __lock)  // 锁,用于多线程环境下的同步
};
typedef struct __dirstream DIR;

closedir() 函数

closedir() 函数用于关闭目录流。其原型如下:

#include <sys/types.h>
#include <dirent.h>int closedir(DIR *dirp);
  • 参数 dirp 是由 opendir() 返回的目录流指针。

  • 返回值为 0 表示成功,返回 -1 表示失败。

读取目录

当我们打开目录文件获取到目录指针时,就可以通过指针遍历到该目录下的所有目录项。

 每个目录项存储了文件的一些基本信息,由 struct dirent 结构体维护,struct dirent 的定义如下:

struct dirent {ino_t d_ino;       // inode 号off_t d_off;       // 当前目录项在目录文件中的位置unsigned short d_reclen; // 目录项记录长度unsigned char d_type;   // 文件类型(如普通文件、目录等)char d_name[256];   // 文件名
};

d_type的可选值如下: 

DT_BLK This is a block device.
DT_CHR This is a character device.
DT_DIR This is a directory.
DT_FIFO This is a named pipe (FIFO).
DT_LNK This is a symbolic link.
DT_REG This is a regular file.
DT_SOCK This is a UNIX domain socket.
DT_UNKNOWN The file type could not be determined.

readdir() 函数

readdir() 函数用于从目录流中读取目录项。其原型如下:

#include <dirent.h>struct dirent *readdir(DIR *dirp);
  • 参数 dirp 是由 opendir() 返回的目录流指针。

  • 返回值是一个指向 struct dirent 的指针,该结构包含目录项的信息,如文件名、文件类型等。

  • 每次调用 readdir() 会返回目录中的下一个目录项,直到返回 NULL,表示目录已遍历完毕。

读取当前目录下所有文件信息示例:

#define ERROR_CHECK(ret,num,msg) {if(ret == num){perror(msg); return -1;}}int main(int argc, char const *argv[])
{DIR *dirp  = opendir("/home/ubuntu/test"); //打开目录ERROR_CHECK(dirp, NULL, "opendir");    //自定义宏函数,用于输出报错信息struct dirent * pdirent;while ((pdirent = readdir(dirp)) !=  NULL)    //读取目录下所有文件信息{printf("inode = %ld, reclen = %d, type = %d, name = %s\n", pdirent->d_ino, pdirent->d_reclen, pdirent->d_type, pdirent->d_name);   }closedir(dirp);return 0;
}

输出如下结果:

inode = 45629913, reclen = 24, type = 4, name = .
inode = 45683356, reclen = 32, type = 8, name = test2.o
inode = 45673789, reclen = 32, type = 8, name = test3.cpp
inode = 45688999, reclen = 32, type = 8, name = test3
inode = 45648948, reclen = 32, type = 8, name = file1
inode = 45620828, reclen = 32, type = 8, name = a.out
inode = 45670364, reclen = 24, type = 8, name = test
inode = 45683251, reclen = 32, type = 8, name = test2
inode = 45659996, reclen = 32, type = 8, name = text.txt
inode = 45687412, reclen = 32, type = 8, name = test3.c
inode = 45683324, reclen = 32, type = 8, name = test2.c
inode = 45641583, reclen = 32, type = 8, name = test2.s
inode = 45658946, reclen = 32, type = 8, name = 54func.h
inode = 45744512, reclen = 32, type = 4, name = 20250531
inode = 45613058, reclen = 24, type = 4, name = ..
inode = 45663197, reclen = 32, type = 8, name = file.tar.gz
inode = 45633873, reclen = 32, type = 8, name = 1_fopen.c
inode = 45661888, reclen = 32, type = 8, name = 1_fopen
inode = 45655606, reclen = 32, type = 8, name = text1.txt

记录位置

telldir() 函数

telldir 函数是 Linux 系统中用于目录操作的一个函数,它用于获取当前目录流的读取位置。这个位置可以用来后续的目录定位操作,例如通过 seekdir 函数返回到该位置。原型如下:

#include <dirent.h>long telldir(DIR *dirp);
  • dirp:指向 DIR 结构的指针,表示目录流。
  • 返回值是一个 long 类型的值,表示当前目录流的读取位置。

  • 如果发生错误,返回值是 -1

 当执行 telldir 获取当前目录流的读取位置。这个位置是一个内部的偏移量,用于标识目录流中当前读取的位置。这个位置可以用于后续的 seekdir 函数调用,从而可以返回到该位置继续读取目录项。

seekdir() 函数

用于设置目录流的读取位置。这个位置通常是通过 telldir 函数获取的偏移量。seekdir 函数允许程序在目录流中跳转到指定的位置,从而可以重新读取目录项或跳过某些目录项。原型如下:

#include <dirent.h>void seekdir(DIR *dirp, long loc);
  • dirp:指向 DIR 结构的指针,表示目录流。这个目录流必须是通过 opendir 函数成功打开的。

  • loc:一个 long 类型的值,表示目录流中的位置偏移量。这个值通常是通过 telldir 函数获取的。

通过 seekdir,程序可以在目录流中自由跳转,从而实现以下功能:

  • 重新读取某个位置的目录项。

  • 跳过某些目录项,直接读取后续的目录项。

注意事项

  • 偏移量的有效性seekdir 函数的偏移量必须是通过 telldir 函数获取的有效值,注意:这里返回的偏移量LOC必须用 long 类型接收。如果使用无效的偏移量,可能导致未定义行为。

  • 目录流的状态seekdir 函数会更新目录流的内部状态,因此在调用 seekdir 之后,目录流的读取位置会改变。

  • 线程安全性:telldir 和 seekdir 函数在多线程环境下是线程安全的,因为每个目录流都有自己的状态信息。

 在之前展示目录信息的基础上,我们记录 test2 文件的位置,并在遍历完目录后重新定位到该文件上并输出信息:

#define ERROR_CHECK(ret,num,msg) {if(ret == num){perror(msg); return -1;}}int main(int argc, char const *argv[])
{DIR *dirp  = opendir("/home/ubuntu/test");ERROR_CHECK(dirp, NULL, "opendir");struct dirent * pdirent;long loc = 0;while ((pdirent = readdir(dirp)) !=  NULL){printf("inode = %ld, reclen = %d, type = %d, name = %s\n", pdirent->d_ino, pdirent->d_reclen, pdirent->d_type, pdirent->d_name);        if(strcmp("test2", pdirent->d_name) == 0){loc = telldir(dirp);}}printf("-----------------\n");seekdir(dirp, loc);pdirent = readdir(dirp);printf("inode = %ld, reclen = %d, type = %d, name = %s\n", pdirent->d_ino, pdirent->d_reclen, pdirent->d_type, pdirent->d_name);closedir(dirp);return 0;
}

结果如下:

inode = 45670364, reclen = 24, type = 8, name = test
inode = 45683251, reclen = 32, type = 8, name = test2
inode = 45659996, reclen = 32, type = 8, name = text.txt
inode = 45687412, reclen = 32, type = 8, name = test3.c
inode = 45683324, reclen = 32, type = 8, name = test2.c
inode = 45641583, reclen = 32, type = 8, name = test2.s
inode = 45658946, reclen = 32, type = 8, name = 54func.h
inode = 45744512, reclen = 32, type = 4, name = 20250531
inode = 45613058, reclen = 24, type = 4, name = ..
inode = 45663197, reclen = 32, type = 8, name = file.tar.gz
inode = 45633873, reclen = 32, type = 8, name = 1_fopen.c
inode = 45661888, reclen = 32, type = 8, name = 1_fopen
inode = 45655606, reclen = 32, type = 8, name = text1.txt
-----------------
inode = 45659996, reclen = 32, type = 8, name = text.txt

可以发现指针遍历到了目标文件的后一个文件,这是因为在调用readdir后,当前目录指针会自动向后偏移到下一个目录项,之后在调用telldir获取偏移量时也就指向了下一个目录的位置。所以在使用telldir记录位置时要注意这一点。

rewinddir() 函数

rewinddir 函数是 Linux 系统中用于目录操作的一个函数,它用于将目录流的读取位置重置到目录的开头。这个函数类似于文件操作中的 rewind 函数,但专门用于目录流。

#include <dirent.h>void rewinddir(DIR *dirp);
  • dirp:指向 DIR 结构的指针,表示目录流。这个目录流必须是通过 opendir 函数成功打开的。

将目录流的读取位置重置到目录的开头。这意味着后续调用 readdir 函数时,将从目录的第一个目录项开始读取。

rewinddir 函数通常用于以下场景:

  • 当需要重新遍历目录时,可以使用 rewinddir 将目录流重置到开头。

  • 在多次遍历目录时,避免关闭和重新打开目录流,从而提高效率。

注意事项

  • 目录流的有效性rewinddir 函数要求目录流是通过 opendir 成功打开的,并且在调用 rewinddir 时目录流仍然有效。

  • 线程安全性rewinddir 函数在多线程环境下是线程安全的,因为每个目录流都有自己的状态信息。

  • 跨平台性rewinddir 函数在 POSIX 系统中是标准的,但在非 POSIX 系统中可能不可用。

补充:文件状态信息

如果我们想进一步的获取文件的详细信息,我们可以调用 stat 类函数来实现

stat() 函数

stat 函数是一个非常重要的系统调用,用于获取文件的状态信息。它提供了关于文件的详细信息,例如文件类型、大小、权限、所有者等。

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>int stat(const char *pathname, struct stat *statbuf);
  • pathname:指向一个以空字符结尾的字符串,表示要获取状态信息的文件路径。

  • buf:指向一个 struct stat 类型的指针,用于存储文件的状态信息。它是一个传入传出参数

stat 函数将文件的状态信息存储在 struct stat 结构体中。该结构体的定义如下:

struct stat {mode_t    st_mode;    // 文件类型和权限ino_t     st_ino;     // inode 编号dev_t     st_dev;     // 设备标识符(文件所在的设备)dev_t     st_rdev;    // 设备标识符(针对特殊文件)nlink_t   st_nlink;    // 硬链接数uid_t     st_uid;     // 文件所有者的用户IDgid_t     st_gid;     // 文件所有者的组IDoff_t     st_size;    // 文件大小(以字节为单位)blksize_t st_blksize; // 文件系统块大小blkcnt_t  st_blocks;  // 分配的块数time_t    st_atime;   // 最后访问时间time_t    st_mtime;   // 最后修改时间time_t    st_ctime;   // 最后状态改变时间
};
  • st_mode:表示文件类型和权限。可以通过宏(如 S_ISDIRS_ISREG 等)来判断文件类型,也可以通过位掩码(如 S_IRUSRS_IWUSR 等)来查看权限。每个宏的具体含义如下所示:

S_IFBLK:  block device
S_IFCHR:  character device
S_IFDIR:  directory
S_IFIFO:  FIFO/pipe
S_IFLNK:  symlink
S_IFREG:  regular file
S_IFSOCK: socket
  • st_ino:inode 编号,是文件系统中唯一标识文件的数字。

  • st_devst_rdev:分别表示文件所在的设备和特殊文件的设备标识符。

  • st_nlink:硬链接数,表示指向该 inode 的硬链接数量。

  • st_uidst_gid:分别表示文件所有者的用户ID和组ID。

  • st_size:文件大小,以字节为单位。

  • st_blksizest_blocks:分别表示文件系统块大小和分配的块数。

  • st_atime``、st_mtimest_ctime:分别表示文件的最后访问时间、最后修改时间和最后状态改变时间。

  • 如果成功,stat 函数返回 0,并将文件的状态信息存储在 buf 指向的结构体中。

  • 如果失败,返回 -1,并设置 errno 以指示错误类型。常见的错误包括:

    • ENOENT:文件或路径不存在。

    • EACCES:权限不足。

    • EFAULTbuf 指针无效。

注意事项

  • stat 函数获取的是文件的元数据,而不是文件内容本身。

  • 如果需要获取符号链接的目标文件的状态信息,可以使用 lstat 函数。

  • 对于某些文件系统,某些字段(如 st_blksize 或st_blocks)可能没有意义。

fstat() 函数

fstat 函数用于获取已打开文件的状态信息。它通过文件描述符来访问文件,而不是通过文件路径。

#include <sys/stat.h>
#include <unistd.h>int fstat(int fd, struct stat *buf);

参数说明

  • fd:文件描述符,是通过 open 函数或其他文件打开操作获得的。

  • buf:指向 struct stat 类型的指针,用于存储文件的状态信息。

返回值

  • 如果成功,返回 0,并将文件的状态信息存储在 buf 指向的结构体中。

  • 如果失败,返回 -1,并设置 errno 以指示错误类型。常见的错误包括:

    • EBADF:无效的文件描述符。

    • EFAULTbuf 指针无效。

注意事项

  • fstat 函数只能用于已经打开的文件,因此需要先通过 open 函数获取文件描述符。

  • fstat 不需要文件路径,因此在处理已打开的文件时效率更高。

lstat() 函数

lstat 函数用于获取符号链接的状态信息,而不是符号链接指向的目标文件的状态信息。如果需要获取目标文件的状态信息,应使用 stat 函数。

#include <sys/stat.h>
#include <unistd.h>int lstat(const char *pathname, struct stat *buf);

参数说明

  • pathname:指向一个以空字符结尾的字符串,表示符号链接的路径。

  • buf:指向 struct stat 类型的指针,用于存储符号链接的状态信息。

返回值

  • 如果成功,返回 0,并将符号链接的状态信息存储在 buf 指向的结构体中。

  • 如果失败,返回 -1,并设置 errno 以指示错误类型。常见的错误包括:

    • ENOENT:文件或路径不存在。

    • EACCES:权限不足。

    • EFAULTbuf 指针无效。

获取文件信息示例:

#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>int main() {struct stat file_stat;const char *file_path = "test1.c";if (stat(file_path, &file_stat) == -1) {perror("stat");return 1;}printf("File size: %ld bytes\n", file_stat.st_size);printf("File permissions: %o\n", file_stat.st_mode & 0777);printf("Number of hard links: %ld\n", file_stat.st_nlink);printf("Owner UID: %d\n", file_stat.st_uid);printf("Owner GID: %d\n", file_stat.st_gid);return 0;
}

输出结果: 

File size: 509 bytes
File permissions: 664
Number of hard links: 1
Owner UID: 1000
Owner GID: 1000

实战1:模拟 ls 命令实现遍历目录下所有文件相关信息

当键入 ls  命令时,可以输出当前目录或指定目录的文件信息:

drwxrwxr-x 3 ubuntu ubuntu  4096  7月  9 15:17 .
drwxrwxr-x 3 ubuntu ubuntu  4096  7月  8 22:03 ..
-rwxrwxr-x 1 ubuntu ubuntu 16312  7月  8 22:45 seekdir
-rw-rw-r-- 1 ubuntu ubuntu   875  7月  8 22:45 seekdir.c
-rwxrwxr-x 1 ubuntu ubuntu 16096  7月  9 15:17 stat
-rw-rw-r-- 1 ubuntu ubuntu   535  7月  9 15:17 stat.c
-rwxrwxr-x 1 ubuntu ubuntu 16136  7月  9 15:15 test1
-rw-rw-r-- 1 ubuntu ubuntu   509  7月  8 22:15 test1.c
drwxrwxr-x 2 ubuntu ubuntu  4096  7月  8 22:04 .vscode

在实现过程中,对于文件权限部分建议逐部分按位处理,用户名和组名可以调用getpwuid和getprgid获取,时间戳可以条用time.h中的函数进行转换

实现:

int main(int argc, char const *argv[])
{ARGS_CHECK(argc, 2);DIR * dirp = opendir(argv[1]);ERROR_CHECK(dirp, NULL, "opendir");struct dirent * pdirent;char buf[256];struct stat file_stat;while ((pdirent = readdir(dirp)) != NULL){sprintf(buf, "%s%s%s", argv[1], "/", pdirent->d_name);int ret = stat(buf, &file_stat);ERROR_CHECK(ret, -1, "stat");switch (file_stat.st_mode & S_IFMT) {case S_IFBLK:  printf("b");   break;case S_IFCHR:  printf("c");   break;case S_IFDIR:  printf("d");   break;case S_IFIFO:  printf("p");   break;case S_IFLNK:  printf("l");   break;case S_IFREG:  printf("-");   break;case S_IFSOCK: printf("s");   break;default:       printf("?");   break;}for(int i = 2; i >= 0; i--){switch((file_stat.st_mode & (7 << (i * 3))) >> (i * 3)){case 0: printf("---");  break;case 1: printf("--x");  break;case 2: printf("-w-");  break;case 3: printf("-wx");  break;case 4: printf("r--");  break;case 5: printf("r-x");  break;case 6: printf("rw-");  break;case 7: printf("rwx");  break;default: break;}}struct passwd * pwd = getpwuid(file_stat.st_uid);struct group * grp  = getgrgid(file_stat.st_gid);struct tm *local_time = localtime(&file_stat.st_mtime);char time_str[80];strftime(time_str, sizeof(time_str), "%b %d %H:%m", local_time);printf(" %ld %s %s %ld %s %s\n",file_stat.st_nlink,pwd->pw_name,grp->gr_name,file_stat.st_size,time_str,pdirent->d_name);}   closedir(dirp);return 0;
}

实现效果:

 实战2:模拟tree命令列出指定目录的层次结构

int tree(const char *path, int level){DIR * dirp = opendir(path);ERROR_CHECK(dirp, NULL, "opendir");struct dirent * pdirent;while((pdirent = readdir(dirp)) != NULL){for(int i = 0; i < level; i++)  printf("   ");printf("%s\n",pdirent->d_name);if(pdirent->d_type == DT_DIR && strcmp(pdirent->d_name,".") != 0 && strcmp(pdirent->d_name,"..") != 0){char newpath[1024];sprintf(newpath,"%s%s%s",path,"/",pdirent->d_name);tree(newpath, level+1);}}closedir(dirp);
}
int main(int argc, char const *argv[])
{ARGS_CHECK(argc, 2);tree(argv[1], 0);return 0;
}

实现效果:

(base) ubuntu@ubuntu:~/MyProject/Linux/dir$ ./tree .
.
stat
ls.c
stat.c
testdir.hello..
.vscode.settings.json..
test1.c
test1
..
tree
ls
seekdir.c
tree.c
seekdir

http://www.dtcms.com/a/270696.html

相关文章:

  • Alloy VS Promtail:基于 Loki 的日志采集架构对比与选型指南
  • ECS由浅入深第四节:ECS 与 Unity 传统开发模式的结合?混合架构的艺术
  • Using Spring for Apache Pulsar:Publishing and Consuming Partitioned Topics
  • vue2 echarts中国地图、在地图上标注经纬度及标注点
  • AI应用实践:制作一个支持超长计算公式的计算器,计算内容只包含加减乘除算法,保存在一个HTML文件中
  • 「macOS 系统字体收集器 (C++17 实现)」
  • Oracle存储过程导出数据到Excel:全面实现方案详解
  • Java零基础笔记08(Java编程核心:面向对象编程高级 {继承、多态})
  • 【macOS】【Swift】【RTF】黑色文字在macOS深色外观下看不清的解决方法
  • yolo8实现目标检测
  • springMVC05-异常处理器
  • HashMap源码分析:put与get方法详解
  • 【拓扑空间】示例及详解1
  • sqlplus表结构查询
  • 高效集成-C#全能打印报表设计器诞生记
  • Android-重学kotlin(协程源码第一阶段)新学习总结
  • mongodb: cannot import name ‘_check_name‘ from ‘pymongo.database‘
  • 池化思想-Mysql异步连接池
  • 教育行业可以采用Html5全链路对视频进行加密?有什么优势?
  • 高通 QCS6490PI 集群架构支撑 DeepSeek 模型稳定运行的技术实现
  • upload-labs靶场通关详解:第19关 条件竞争(二)
  • Java-----韩顺平单例设计模式学习笔记
  • java项目maven编译的时候报错:Fatal error compiling: 无效的标记: --release
  • 【计算机组成原理——知识点总结】-(总线与输入输出设备)-学习笔记总结-复习用
  • Caffeine的tokenCache与Spring的CaffeineCacheManager缓存区别
  • uniapp,Anroid10+版本如何保存图片并删除
  • 缓存三大问题详解与工业级解决方案
  • 视频音频转换器V!P版(安卓)安装就解锁V!P!永久免费使用!
  • 【RK3568+PG2L50H开发板实验例程】FPGA部分 | DDR3 读写实验例程
  • 创客匠人:在 IP 变现浪潮中,坚守知识变现的本质