http://blog.youkuaiyun.com/vinda2012/article/details/6091739
前几天需要实现对整个目录树的遍历,查阅了相关的一些资料。开始找到的原始的方法是使用readdir()与lstat()函数实现递归遍历,后来发现linux对于目录遍历这种最常用的操作已经提供了很完善的接口:ftw()与nftw()。下面就这两种方法具体说明一下。
1、手动实现递归
1.1 stat()函数族
stat函数族包括:stat,fstat以及lstat函数,都是向用户返回文件的属性信息(元数据)。
- #include <sys/stat.h>
- int stat(const char*pathname,struct stat*buf);
- int fstat(int filedes,struct stat*buf);
- int lstat(const char *pathname,struct stat*buf);
三个函数的返回:若成功为0,出错为-1。对一个pathname,stat函数返回一个与此命名文件有关的信息结构,fstat函数获得已在描述符filedes上打开的文件的有关信息。 lstat函数类似于stat,但是当命名的文件是一个符号连接时,lstat返回该符号连接的有关信息,而不是由该符号连接引用的文件的信息。
第二个参数是个指针,它指向一个我们应提供的结构。这些函数填写由buf指向的结构。该结构的实际定义可能所实施而有所不同,但其基本形式是:
struct stat{
mode st_mode; /*文件类型和方式(许可数)*/
ino st_ino;/* i-节点号(序列号)*/
dev st_dev;/*设备号(文件系统)*/
dev st_rdev;/*特殊文件的设备号*/
nlink st_nlink;/*连接数*/
uid st_uid;/*属主的用户ID*/
gid st_gid;/*属主的组ID*/
off st_size;/*普通文件的字节长度*/
time st_atime;/*最后存取时间*/
time st_mtime;/*最后修改存取时间*/
time st_ctime;/*最后文件状态更改时间*/
long st_blksize;/*最佳I/O块长*/
long st_blocks;/*分配的512字节块块数
};
下面是一个简单的测试
- #include<unistd.h>
- #include<sys/stat.h>
- #include<stdio.h>
- int
- main(int argc, char **argv){
- struct stat buf;
- if(stat(argv[1],&buf)) {
- printf("[stat]:error!/n");
- return -1;
- }
- printf("st_dev:%d/n",buf.st_dev);
- printf("st_ino:%d/n",buf.st_ino);
- printf("st_mode:%d S_ISDIR:%d/n",buf.st_mode,S_ISDIR(buf.st_mode));
- printf("st_nlink:%d/n",buf.st_nlink);
- printf("st_uid:%d/n",buf.st_uid);
- printf("st_gid:%d/n",buf.st_gid);
- printf("st_rdev:%d/n",buf.st_rdev);
- printf("st_size:%d/n",buf.st_size);
- printf("st_blksize:%lu/n",buf.st_blksize);
- printf("st_blocks:%lu/n",buf.st_blocks);
- printf("st_atime:%ld/n",buf.st_atime);
- printf("st_mtime:%ld/n",buf.st_mtime);
- printf("st_ctime:%ld/n",buf.st_ctime);
- return 0;
- }
这里补充说明一下linux中文件的基本类型。
1.普通文件(Regular file)。这是最常见的文件类型,这种文件包含了某种形式的数据。至于这种数据是文本还是二进制数据对于系统核而言并无区别。对普通文件内容的解释由处理该文件的应用程序进行。
2.目录文件(Directory file)。这种文件包含了其它文件的名字以及指向与这些文件有关信息的指针。对一个目录文件具有读许可数的任一进程都可以读该目录的内容,但只有系统核可以写目录文件。
3.字符特殊文件(Charocter special file)。这种文件用于系统中的某些类型的设备。
4.块特殊文件(Block special file)。这种文件典型地用于磁盘设备。系统中的所有设备或者是字符特殊文件,或者是块特殊文件。
5.FIFO。这种文件用于进程间的通信,有时也将其称为命名管道。
6.套接口(socket)。这种文件用于进程间的网络通信。套接口也可用于在一台宿主机上的进程之间的非网络通信。
7.符号连接(Symboliclink)。这种文件指向另一个文件。
对于文件类型,可以利用定义的宏比如S_ISDIR()等测试st_mode,判断文件类型。宏有S_ISREG、S_ISDIR、S_ISCHR、S_ISBLK、S_ISFIFO、S_ISLNK、S_ISSOCK。
1.2 遍历目录例子
引用别人的一个例子,现在把许多文件处理函数集中在一起使用,程序遍历指定目录的文件,同时也要进到下级子目录中进行遍历,这一点是将子目录递归传递到opendir中去,需要指出的是,这就决定了如果子目录嵌套过深,程序将失败返回,因为允许打开的子目录流数量是有上限的。
- /* We start with the appropriate headers and then a function, printdir,
- which prints out the current directory.
- It will recurse for subdirectories, using the depth parameter is used for indentation. */
- #include <unistd.h>
- #include <stdio.h>
- #include <dirent.h>
- #include <string.h>
- #include <sys/stat.h>
- void printdir(char *dir, int depth)
- {
- DIR *dp;
- struct dirent *entry;
- struct stat statbuf;
- if((dp = opendir(dir)) == NULL) {
- fprintf(stderr,"cannot open directory: %s/n", dir);
- return;
- }
- chdir(dir);
- while((entry = readdir(dp)) != NULL) {
- lstat(entry->d_name,&statbuf);
- if(S_ISDIR(statbuf.st_mode)) {
- /**//* Found a directory, but ignore . and .. */
- if(strcmp(".",entry->d_name) == 0 ||
- strcmp("..",entry->d_name) == 0)
- continue;
- printf("%*s%s//n",depth,"",entry->d_name);
- /**//* Recurse at a new indent level */
- printdir(entry->d_name,depth+4);
- }
- else printf("%*s%s/n",depth,"",entry->d_name);
- }
- chdir("..");
- closedir(dp);
- }
- /**//* Now we move onto the main function. */
- int main(int argc, char* argv[])
- {
- char *topdir, pwd[2]=".";
- if (argc != 2)
- topdir=pwd;
- else
- topdir=argv[1];
- printf("Directory scan of %s/n",topdir);
- printdir(topdir,0);
- printf("done./n");
- exit(0);
- }