Linux下C语言递归遍历一个目录

女王节快乐(^-^)

Posted by 婷 on March 8, 2020 本文总阅读量

今天在《Linux程序设计》中看到了一段代码,用来实现递归遍历一个目录下所有的文件并打印到标准输出(也就是屏幕终端)。因为用到了很多文件操作的函数,所以记录下来自己的学习过程。

代码内容

scan.cpp:

#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void printfdir(char *dir,int depth)
{
    DIR* dp;//DIR结构指针
    struct dirent *entry;//dirent结构指针
  
    if ((dp=opendir(dir))==NULL)//打开文件失败
    {
        fprintf(stderr,"can not open this dir:%s\n",dir);//打印到标准错误,也就是在屏幕上输出错误信息
        return;//退出
    }
    
    chdir(dir);//进入目录
    while ((entry=readdir(dp))!=NULL)//循环遍历
    {
        if (entry->d_type==DT_DIR)//如果是目录
        {
          if (strcmp(".",entry->d_name)==0 || strcmp("..",entry->d_name)==0)//因为已经遍历过. ..两个目录了排除这两个目录
            {
                continue;
            }
            printf("%*s%s\n",depth," ",entry->d_name);
            printfdir(entry->d_name,depth+4);//递归调用
        }
        else//不是目录就是文件
        {
            printf("%*s%s\n",depth," ",entry->d_name);
        }
    }
    chdir("..");//返回上级目录
    closedir(dp);//关闭当前目录流
}

int main(int argc,char *argv[])
{
    char *topdir=".";
    if (argc>=2)
    {
        topdir=argv[1];
    }
    printf("directory scan of %s:\n",topdir);
    printfdir(topdir,0);
    printf("done\n");
    exit(0);
}

main函数

首先从main函数看,发现跟以往不同的是多了两个参数。

C语言规定main函数可以有两个参数,第一个形参是int类型的变量,另一个是char **(char *[])类型的变量。一般把这两个参数叫做argcargv

在Linux下,argc表示命令行的参数个数,包括执行程序的名字;argv表示命令行的各个参数。

#include <iostream>
using namespace std;
int main(int argc,char *argv[])
{
    cout<<"main function parameters'num:"<<argc<<endl;
    for (int i = 0; i < argc; i++)
    {
        cout<<"parameter"<<i+1<<" is :"<<argv[i]<<endl;
    }
    return 0;
}

运行结果如下:

1.png

scan.cppmain函数将从命令行读入多个参数,如果只有一个参数,则将.传递给printfdir函数,也就是当前目录,如果是有多的参数,则传递第二个参数。

printfdir函数

函数入口参数有两个,char *dir传入要遍历的目录名字,depth表示最后程序打印出来的空格的缩排。

  • DIR* dp;

    Linux下使用DIR结构作为目录操作的基础,在dirent.h头文件中定义。函数原型:

    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;  
    

    DIR结构的数据项则返回到dirent结构。

  • struct dirent *entry;

    dirent结构也在dirent.ht头文件内定义,包含目录内的内容,反应文件的一些信息,结构体中的内容如下

    struct dirent
      {
        __ino_t d_ino;//文件的inode节点号
        __off_t d_off;
        unsigned short int d_reclen;//文件名字长度
        unsigned char d_type; //文件类型 
        char d_name[256];		//文件名字    /* We must not include limits.h! */
      };
    
  • opendir函数

    函数原型:DIR *opendir(const char *name)

    形参:要打开的目录的名字

    返回值:为打开的目录创建一个目录流。成功则返回一个DIR指针,失败返回NULL

  • chdir函数

    这个命令就熟悉得不能再熟悉了,就是cd命令,进入一个目录

  • readdir函数

    函数原型:struct dirent*readdir(DIR *dirp)

    形参:要读取的目录流

    返回值:读取成功则返回dirent结构指针,失败或者到达目录尾则返回NULL

  • closedir函数

    关闭一个目录流

  • stat结构S_ISDIR宏定义

    在书中其实在判断一个文件名是否是目录的时候,用的是一个stat结构的st_mode以及S_ISDIR的宏定义,后来我换成用dirent结构中的d_type,觉得会简便点

    原程序部分内容如下:

    ...... 
    chdir(dir);
    struct stat statbuff;//声明stat结构变量statbuff,存放着比dirent结构更多关于目录文件的信息
        while ((entry=readdir(dp))!=NULL)
        {
             lstat(entry->d_name,&statbuff);//statbuff获取文件名,内部存储着该文件名的信息
             if (S_ISDIR(statbuff.st_mode) )//通过S_ISDIR宏定义判断
            {
                if (  strcmp(".",entry->d_name)==0    ||     strcmp("..",entry->d_name)==0  )
                {
                    continue;
                }
    ......    
    
    • stat结构定义于/sys/stat.h头文件中,结构体中的内容如下:

      struct stat {
        mode_t st_mode; //文件对应的模式,文件,目录等
        ino_t st_ino; //i-node节点号
        dev_t st_dev; //设备号码
        dev_t st_rdev; //特殊设备号码
        nlink_t st_nlink; //文件的连接数
        uid_t st_uid; //文件所有者
        gid_t st_gid; //文件所有者对应的组
        off_t st_size; //普通文件,对应的文件字节数
        time_t st_atime; //文件最后被访问的时间
        time_t st_mtime; //文件内容最后被修改的时间
        time_t st_ctime; //文件状态(属性)改变时间
        blksize_t st_blksize; //文件内容对应的块大小
        blkcnt_t st_blocks; //文件内容对应的块数量
        };
      

      在这里用到的是第一个成员变量st_mode

    • lstat函数

      函数原型:int lstat(const char *path,struct stat *buf)

      参数:第一个是文件名,第二个是一个stat结构

    • 判断是否是一个目录,其他类似的宏定义有

      S_ISLNK是否是一个连接.
          
      S_ISREG是否是一个常规文件.
          
      S_ISDIR是否是一个目录
          
      S_ISCHR是否是一个字符设备.
          
      S_ISBLK是否是一个块设备
          
      S_ISFIFO是否是一个FIFO文件.
          
      S_ISSOCK是否是一个SOCKET文件. 
      

运行结果

遍历当前目录

./scan或者./scan .

2.png

遍历./home目录

$ ./scan /home

如果觉得要遍历之后输出的内容太多可以将程序的输出重定向到另一个文本文件或者用管道命令结合more分页查看,按q退出也很方便。

$ ./scan /home > file1
#或者
$ ./scan /home | more

3.png

4.png

学习心得

在这种函数比较多参数又比较复杂的情况下,多看函数原型,一点点的去分析函数参数跟返回值,会更容易帮助自己快速读懂程序。