前幾天需要實(shí)現(xiàn)對(duì)整個(gè)目錄樹的遍歷,查閱了相關(guān)的一些資料。開始找到的原始的方法是使用readdir()與lstat()函數(shù)實(shí)現(xiàn)遞歸遍歷,后來發(fā)現(xiàn)linux對(duì)于目錄遍歷這種最常用的操作已經(jīng)提供了很完善的接口:ftw()與nftw()。下面就這兩種方法具體說明一下。
1、手動(dòng)實(shí)現(xiàn)遞歸
1.1 stat()函數(shù)族
stat函數(shù)族包括:stat,fstat以及l(fā)stat函數(shù),都是向用戶返回文件的屬性信息(元數(shù)據(jù))。
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);
三個(gè)函數(shù)的返回:若成功為0,出錯(cuò)為-1。對(duì)一個(gè)pathname,stat函數(shù)返回一個(gè)與此命名文件有關(guān)的信息結(jié)構(gòu),fstat函數(shù)獲得已在描述符filedes上打開的文件的有關(guān)信息。 lstat函數(shù)類似于stat,但是當(dāng)命名的文件是一個(gè)符號(hào)連接時(shí),lstat返回該符號(hào)連接的有關(guān)信息,而不是由該符號(hào)連接引用的文件的信息。
第二個(gè)參數(shù)是個(gè)指針,它指向一個(gè)我們應(yīng)提供的結(jié)構(gòu)。這些函數(shù)填寫由buf指向的結(jié)構(gòu)。該結(jié)構(gòu)的實(shí)際定義可能所實(shí)施而有所不同,但其基本形式是:
struct stat{
mode st_mode; /*文件類型和方式(許可數(shù))*/
ino st_ino;/* i-節(jié)點(diǎn)號(hào)(序列號(hào))*/
dev st_dev;/*設(shè)備號(hào)(文件系統(tǒng))*/
dev st_rdev;/*特殊文件的設(shè)備號(hào)*/
nlink st_nlink;/*連接數(shù)*/
uid st_uid;/*屬主的用戶ID*/
gid st_gid;/*屬主的組ID*/
off st_size;/*普通文件的字節(jié)長(zhǎng)度*/
time st_atime;/*最后存取時(shí)間*/
time st_mtime;/*最后修改存取時(shí)間*/
time st_ctime;/*最后文件狀態(tài)更改時(shí)間*/
long st_blksize;/*最佳I/O塊長(zhǎng)*/
long st_blocks;/*分配的512字節(jié)塊塊數(shù)
};
下面是一個(gè)簡(jiǎn)單的測(cè)試
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;
}
這里補(bǔ)充說明一下linux中文件的基本類型。
1.普通文件(Regular file)。這是最常見的文件類型,這種文件包含了某種形式的數(shù)據(jù)。至于這種數(shù)據(jù)是文本還是二進(jìn)制數(shù)據(jù)對(duì)于系統(tǒng)核而言并無區(qū)別。對(duì)普通文件內(nèi)容的解釋由處理該文件的應(yīng)用程序進(jìn)行。
2.目錄文件(Directory file)。這種文件包含了其它文件的名字以及指向與這些文件有關(guān)信息的指針。對(duì)一個(gè)目錄文件具有讀許可數(shù)的任一進(jìn)程都可以讀該目錄的內(nèi)容,但只有系統(tǒng)核可以寫目錄文件。
3.字符特殊文件(Charocter special file)。這種文件用于系統(tǒng)中的某些類型的設(shè)備。
4.塊特殊文件(Block special file)。這種文件典型地用于磁盤設(shè)備。系統(tǒng)中的所有設(shè)備或者是字符特殊文件,或者是塊特殊文件。
5.FIFO。這種文件用于進(jìn)程間的通信,有時(shí)也將其稱為命名管道。
6.套接口(socket)。這種文件用于進(jìn)程間的網(wǎng)絡(luò)通信。套接口也可用于在一臺(tái)宿主機(jī)上的進(jìn)程之間的非網(wǎng)絡(luò)通信。
7.符號(hào)連接(Symboliclink)。這種文件指向另一個(gè)文件。
對(duì)于文件類型,可以利用定義的宏比如S_ISDIR()等測(cè)試st_mode,判斷文件類型。宏有S_ISREG、S_ISDIR、S_ISCHR、S_ISBLK、S_ISFIFO、S_ISLNK、S_ISSOCK。
1.2 遍歷目錄例子
引用別人的一個(gè)例子,現(xiàn)在把許多文件處理函數(shù)集中在一起使用,程序遍歷指定目錄的文件,同時(shí)也要進(jìn)到下級(jí)子目錄中進(jìn)行遍歷,這一點(diǎn)是將子目錄遞歸傳遞到opendir中去,需要指出的是,這就決定了如果子目錄嵌套過深,程序?qū)⑹》祷兀驗(yàn)樵试S打開的子目錄流數(shù)量是有上限的。
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調(diào)用遍歷目錄
2.1ftw函數(shù)族
使用readdir函數(shù)等實(shí)現(xiàn)遞歸遍歷目錄樹的方法比較原始,glibc2.1收錄了ftw等函數(shù),可以方便實(shí)現(xiàn)目錄樹的遍歷。
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()
函數(shù)說明:ftw() 會(huì)從參數(shù)dirpath指定的目錄開始,往下一層層地遞歸式遍歷子目錄。ftw()會(huì)傳三個(gè)參數(shù)給fn(), 第一個(gè)參數(shù)*fpath指向當(dāng)時(shí)所在的目錄路徑,第二個(gè)參數(shù)是*sb, 為stat結(jié)構(gòu)指針,第三個(gè)參數(shù)為flag,有下面幾種可能值
FTW_F 一般文件
FTW_D 目錄
FTW_DNR 不可讀取的目錄,此目錄以下將不被遍歷
FTW_SL 符號(hào)連接
FTW_NS 無法取得stat結(jié)構(gòu)數(shù)據(jù),有可能是權(quán)限問題
最后一個(gè)參數(shù)depth代表ftw()在進(jìn)行遍歷目錄時(shí)同時(shí)打開的文件數(shù)。ftw()在遍歷時(shí)每一層目錄至少需要一個(gè)文件描述詞,如果遍歷時(shí)用完了depth所給予的限制數(shù)目,整個(gè)遍歷將因不斷地關(guān)文件和開文件操作而顯得緩慢。(實(shí)際做測(cè)試的時(shí)候未發(fā)現(xiàn)...)
如果要結(jié)束ftw()的遍歷,fn()只需返回一非零值即可,此值同時(shí)也會(huì)是ftw()的返回值。否則ftw()會(huì)試著走完所有的目錄,然后返回0
返 回 值:遍歷中斷則返回fn()函數(shù)的返回值,全部遍歷則返回0,若有錯(cuò)誤發(fā)生則返回-1
附加說明:由于ftw()會(huì)動(dòng)態(tài)配置內(nèi)存使用,請(qǐng)使用正常方式(fn函數(shù)返回非零值)來中斷遍歷,不要在fn函數(shù)中使用longjmp()
nftw()
函數(shù)說明:nftw()與ftw()很像,都是從參數(shù)dirpath指定的目錄開始, 往下一層層地遞歸遍歷子目錄。 每進(jìn)入一個(gè)目錄,便會(huì)調(diào)用參數(shù)*fn定義的函數(shù)來處理。nftw()會(huì)傳四個(gè)參數(shù)給fn(). 第一個(gè)參數(shù)*fpath指向當(dāng)時(shí)所在的目錄路徑,第二個(gè)參數(shù)是*sb, 為stat結(jié)構(gòu)指針(結(jié)構(gòu)定義請(qǐng)參考stat()),第三個(gè)參數(shù)為typeflag,有底下幾種可能值:
FTW_F 一般文件
FTW_D 目錄
FTW_DNR 不可讀取的目錄。此目錄以下將不被遍歷
FTW_SL 符號(hào)連接
FTW_NS 無法取得stat結(jié)構(gòu)數(shù)據(jù),在可能是權(quán)限問題
FTW_DP 目錄,而且子目錄都已被遍歷過了
FTW_SLN 符號(hào)連接,但連接不存在的文件
fn()的第四個(gè)參數(shù)是FTW結(jié)構(gòu),定義如下:
struct FTW
{
int base;
int level; //level代表遍歷時(shí)的深度
}
nftw()第三個(gè)參數(shù)depth代表nftw()在進(jìn)行遍歷目錄時(shí)可同時(shí)打開的文件數(shù)。
ftw()在遍歷時(shí)每一層目錄至少需要一個(gè)文件描述詞,如果遍歷時(shí)用完了depth所給予的限制數(shù)目,整個(gè)遍歷將因不斷地關(guān)文件和開文件操作而顯得的緩慢
nftw()最后一個(gè)參數(shù)flags用來指定遍歷時(shí)的動(dòng)作,可指定下列的操作或用OR組合
FTW_CHDIR 在讀目錄之前先用chdir()移到此目錄
FTW_DEPTH 執(zhí)行深度優(yōu)先搜索。在遍歷此目錄前先將所有子目錄遍歷完
FTW_MOUNT 遍歷時(shí)不要跨越到其他文件系統(tǒng)
FTW_PHYS 不要遍歷符號(hào)連接的目錄。預(yù)設(shè)會(huì)遍歷符號(hào)連接目錄
如果要結(jié)束nftw()的遍歷,fn()只需返回一非0值即可,此值同時(shí)也會(huì)是nftw()的返回值。否則nftw()會(huì)試著遍歷完所有目錄,然后返回0.
返 回 值 :遍歷中斷則返回fn()函數(shù)的返回值, 全部遍歷完則返回0,若有錯(cuò)誤發(fā)生則返回-1
區(qū)別:ftw 對(duì)于每一個(gè)文件他都會(huì)調(diào)用stat函數(shù),這就造成程序會(huì)跟隨符號(hào)鏈接。這就可能導(dǎo)致在某些情況下重復(fù)某些目錄或者循環(huán)統(tǒng)計(jì)某些目錄文件(這是因?yàn)榉?hào)鏈接的原因,詳細(xì)參見UNIX環(huán)境高級(jí)編程)。
nftw將調(diào)用lstat函數(shù)所以不存在跟隨符號(hào)鏈接的問題。
注意:使用nftw函數(shù)時(shí),必須定義#define _XOPEN_SOURCE 500,否則會(huì)出現(xiàn)未定義等錯(cuò)誤。
有一個(gè)沒搞清楚的問題是我使用FTW_DEPTH 來遍歷整個(gè)目錄樹的時(shí)候,遍歷到proc目錄下存在異常返回,可能還需要指定FTW_PHYS使其不遍歷符號(hào)鏈接目錄,這個(gè)有空查一下。
2、遍歷的例子
自己寫的一個(gè)測(cè)試的小例子。遍歷指定目錄,輸出文件元數(shù)據(jù)和遍歷深度等信息。
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;
}