1. 程式人生 > >linux下快速遍歷目錄樹方法

linux下快速遍歷目錄樹方法

個人使用nftw()遍歷指定目錄的大小: 具體用法請參照下文說明

#include <stdio.h>
#include <ftw.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#define _XOPEN_SOURCE 500
#define OPENNUMS 1024
off_t total_size;
int back(const char *dirpath,const struct stat *pstat ,int flags ,struct FTW *s)
{
	if((flags == FTW_D) || (flags == FTW_F)){
		total_size += pstat->st_size;
	}
	return 0;
}
int main(int argc , char *argv[])
{
	if(argc > 3 || argc < 2){
		printf("Error argument!");
		return -1;
	}
	if(argc == 2){
		int ftw_res = 0;
		char *path = argv[1];
		struct stat buf;
		if(lstat(path,&buf) < 0){
			printf("lstat error!\n");
			return -1;
		}
		if(!S_ISDIR(buf.st_mode)){
			printf("Not a directory!\n");
			return -1;
		}
		int res = nftw(path,back,OPENNUMS,1/*FTW_PHYS*/);
		if(res < 0){
			printf("nftw error!\n");
			return -1;
		}else{
			printf("total size : %ld\n",total_size);
			return 0;
		}
	}
	return 0;
}

前幾天需要實現對整個目錄樹的遍歷,查閱了相關的一些資料。開始找到的原始的方法是使用readdir()與lstat()函式實現遞迴遍歷,後來發現linux對於目錄遍歷這種最常用的操作已經提供了很完善的介面:ftw()與nftw()。下面就這兩種方法具體說明一下。

1、手動實現遞迴

1.1 stat()函式族

stat函式族包括:stat,fstat以及lstat函式,都是向用戶返回檔案的屬性資訊(元資料)。

view plaincopy to clipboardprint?
#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);  
 #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位元組塊塊數
        };

下面是一個簡單的測試

view plaincopy to clipboardprint?
#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;   
}  
#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中去,需要指出的是,這就決定了如果子目錄巢狀過深,程式將失敗返回,因為允許開啟的子目錄流數量是有上限的。

view plaincopy to clipboardprint?
/*  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);   
}  
/*  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);
}

2、使用ftw呼叫遍歷目錄

2.1ftw函式族

    使用readdir函式等實現遞迴遍歷目錄樹的方法比較原始,glibc2.1收錄了ftw等函式,可以方便實現目錄樹的遍歷。

view plaincopy to clipboardprint?
#include <ftw.h>   
int ftw(const char *dirpath,   
        int (*fn) (const char *fpath, const struct stat *sb,int typeflag),   
        int nopenfd);   
#define _XOPEN_SOURCE 500   
#include <ftw.h>   
int nftw(const char *dirpath,   
        int (*fn) (const char *fpath, const struct stat *sb,int typeflag, struct FTW *ftwbuf),   
        int nopenfd, int flags);  
#include <ftw.h>
int ftw(const char *dirpath,
        int (*fn) (const char *fpath, const struct stat *sb,int typeflag),
        int nopenfd);
#define _XOPEN_SOURCE 500
#include <ftw.h>
int nftw(const char *dirpath,
        int (*fn) (const char *fpath, const struct stat *sb,int typeflag, struct FTW *ftwbuf),
        int nopenfd, int flags);

具體的英文解釋可以參考文章《 ftw, nftw - file tree walk 》。

ftw()


函式說明:ftw() 會從引數dirpath指定的目錄開始,往下一層層地遞迴式遍歷子目錄。ftw()會傳三個引數給fn(), 第一個引數*fpath指向當時所在的目錄路徑,第二個引數是*sb, 為stat結構指標,第三個引數為flag,有下面幾種可能值
FTW_F        一般檔案
FTW_D       目錄
FTW_DNR    不可讀取的目錄,此目錄以下將不被遍歷
FTW_SL       符號連線
FTW_NS       無法取得stat結構資料,有可能是許可權問題

      最後一個引數depth代表ftw()在進行遍歷目錄時同時開啟的檔案數。ftw()在遍歷時每一層目錄至少需要一個檔案描述詞,如果遍歷時用完了depth所給予的限制數目,整個遍歷將因不斷地關檔案和開檔案操作而顯得緩慢。(實際做測試的時候未發現...)
      如果要結束ftw()的遍歷,fn()只需返回一非零值即可,此值同時也會是ftw()的返回值。否則ftw()會試著走完所有的目錄,然後返回0

返 回  值:遍歷中斷則返回fn()函式的返回值,全部遍歷則返回0,若有錯誤發生則返回-1
附加說明:由於ftw()會動態配置記憶體使用,請使用正常方式(fn函式返回非零值)來中斷遍歷,不要在fn函式中使用longjmp()

nftw()


函式說明:nftw()與ftw()很像,都是從引數dirpath指定的目錄開始, 往下一層層地遞迴遍歷子目錄。 每進入一個目錄,便會呼叫引數*fn定義的函式來處理。nftw()會傳四個引數給fn(). 第一個引數*fpath指向當時所在的目錄路徑,第二個引數是*sb, 為stat結構指標(結構定義請參考stat()),第三個引數為typeflag,有底下幾種可能值:
FTW_F                         一般檔案
FTW_D                         目錄
FTW_DNR                      不可讀取的目錄。此目錄以下將不被遍歷
FTW_SL                         符號連線
FTW_NS                        無法取得stat結構資料,在可能是許可權問題
FTW_DP                        目錄,而且子目錄都已被遍歷過了
FTW_SLN                       符號連線,但連線不存在的檔案

fn()的第四個引數是FTW結構,定義如下:

struct  FTW
{
     int  base;
     int  level; //level代表遍歷時的深度
}

nftw()第三個引數depth代表nftw()在進行遍歷目錄時可同時開啟的檔案數。

ftw()在遍歷時每一層目錄至少需要一個檔案描述詞,如果遍歷時用完了depth所給予的限制數目,整個遍歷將因不斷地關檔案和開檔案操作而顯得的緩慢

nftw()最後一個引數flags用來指定遍歷時的動作,可指定下列的操作或用OR組合
FTW_CHDIR                 在讀目錄之前先用chdir()移到此目錄
FTW_DEPTH                執行深度優先搜尋。在遍歷此目錄前先將所有子目錄遍歷完
FTW_MOUNT               遍歷時不要跨越到其他檔案系統
FTW_PHYS                  不要遍歷符號連線的目錄。預設會遍歷符號連線目錄

如果要結束nftw()的遍歷,fn()只需返回一非0值即可,此值同時也會是nftw()的返回值。否則nftw()會試著遍歷完所有目錄,然後返回0.

返 回 值 :遍歷中斷則返回fn()函式的返回值, 全部遍歷完則返回0,若有錯誤發生則返回-1

區別:ftw 對於每一個檔案他都會呼叫stat函式,這就造成程式會跟隨符號連結。這就可能導致在某些情況下重複某些目錄或者迴圈統計某些目錄檔案(這是因為符號連結的原因,詳細參見UNIX環境高階程式設計)。

 nftw將呼叫lstat函式所以不存在跟隨符號連結的問題。

   有一個沒搞清楚的問題是我使用FTW_DEPTH 來遍歷整個目錄樹的時候,遍歷到proc目錄下存在異常返回,可能還需要指定FTW_PHYS使其不遍歷符號連結目錄,這個有空查一下。

2、遍歷的例子

    自己寫的一個測試的小例子。遍歷指定目錄,輸出檔案元資料和遍歷深度等資訊。

view plaincopy to clipboardprint?
#define _XOPEN_SOURCE 500    
#include<ftw.h>   
#include<sys/stat.h>   
#include<unistd.h>   
#include<stdio.h>   
#include<string.h>    
#define FILEOPEN 1024    
int gb_filecount;   
int getMetadata(const char *dirpath, const struct stat *sb,int typeflag, struct FTW *ftwbuf);   
int main(int argc, char ** argv){   
     
  int ret = 0;   
  struct stat pathbuf;   
  if(argc > 2){   
    printf("-nfwt_t:invalid arguments /n ");   
    return -1;   
  }   
  if(stat(argv[1],&pathbuf)){   
    printf("-nfwt_t:invalid dirpath:%s/n",argv[1]);   
    return -1;   
  }else{   
    if(0 == S_ISDIR(pathbuf.st_mode)){   
      printf("-nfwt_t:/"%s/" is not dirpath/n",argv[1]);   
      return -1;   
    }   
  }   
  gb_filecount=0;   
  ret = nftw(argv[1],getMetadata,FILEOPEN,FTW_PHYS);   
    if(ret<0){   
    printf("-nftw:[wrong:%d]ntfw search %d files/n",ret,gb_filecount);   
    return -1;   
  }else{   
    printf("-nftw:[done:%d]trasvers in %s search %d files/n",ret,argv[1],gb_filecount);   
    return 0;   
  }   
}   
int    
getMetadata(const char *dirpath, const struct stat *sb,int typeflag, struct FTW *ftwbuf){   
  printf("num:%d path:%s ",++gb_filecount,dirpath);   
  printf("st_dev:%d ",(*sb).st_dev);   
  printf("st_ino:%d ",(*sb).st_ino);   
  printf("st_mode:%d S_ISDIR:%d ",(*sb).st_mode,S_ISDIR((*sb).st_mode));   
  printf("st_nlink:%d ",(*sb).st_nlink);   
  printf("st_uid:%d ",(*sb).st_uid);   
  printf("st_gid:%d ",(*sb).st_gid);   
  printf("st_rdev:%d ",(*sb).st_rdev);   
  printf("st_size:%d ",(*sb).st_size);   
  printf("st_blksize:%lu ",(*sb).st_blksize);   
  printf("st_blocks:%lu ",(*sb).st_blocks);   
  printf("st_atime:%ld ",(*sb).st_atime);   
  printf("st_mtime:%ld ",(*sb).st_mtime);   
  printf("st_ctime:%ld ",(*sb).st_ctime);   
  printf("typeflag:%d ",typeflag);   
  printf("FTW_base:%d FTW_level:%d /n",(*ftwbuf).base,(*ftwbuf).level);   
  return 0;