在Linux下和Windows下遍歷目錄的方法及如何達成一致性操做

    最近由於測試目的須要遍歷一個目錄下面的全部文件進行操做,主要是讀每一個文件的內容,只要知道文件名就OK了。在Java中直接用File類就能夠搞定,由於Java中使用了組合模式,使得客戶端對單個文件和文件夾的使用具備一致性,很是方便。但在C中就不同了,並且在不一樣的平臺下使用方法也不一樣。在Linux下實現該功能就很是方便,由於自帶有API庫,幾個函數用起來駕輕就熟(雖然有些小問題,後面說),在Windows下實現就不是那麼方便,雖然也有本身的API,但用法有些晦澀難懂,由於沒有封裝起來,須要本身一步一步進行操做,由於用的是Windows API庫函數因此若是對Windows編程不熟悉的話,照搬網上的代碼錯了也不易調試。爲此,我把這些操做都封裝成相似Linux下的庫函數,一方面簡化透明瞭操做,另外一方面(也許更重要)就是移植性,這樣將包含該功能的程序從Windows上移植到Linux下就無需改動代碼了(刪掉實現封裝的文件,由於Linux下自帶了),固然從Linux下移植到Windows下一樣方便(增長實現封裝的文件便可),這就是所謂的OCP原則吧(開放封閉原則,具體見:程序員該有的藝術氣質—SOLID原則)。好了,首先看下Linux下是如何實現這個功能的。html

1、Linux下遍歷目錄的方法

 Linux下實現目錄操做的API函數都在頭文件dirent.h中,截取部分該文件內容以下:node

/** structure describing an open directory. */
typedef struct _dirdesc {
    int    dd_fd;          /** file descriptor associated with directory */
    long    dd_loc;        /** offset in current buffer */
    long    dd_size;       /** amount of data returned by getdirentries */
    char    *dd_buf;       /** data buffer */
    int    dd_len;         /** size of data buffer */
    long    dd_seek;       /** magic cookie returned by getdirentries */
    long    dd_rewind;     /** magic cookie for rewinding */
    int    dd_flags;       /** flags for readdir */
    struct pthread_mutex    *dd_lock;    /** lock */
    struct _telldir *dd_td;    /** telldir position recording */
} DIR;

typedef    void *    DIR;

DIR    *opendir(const char *);
DIR    *fdopendir(int);
struct dirent *readdir(DIR *);
void     seekdir(DIR *, long);
long     telldir(DIR *);
void rewinddir(DIR *); int closedir(DIR *);

struct dirent
{
     long d_ino;              /* inode number*/
     off_t d_off;             /* offset to this dirent*/
     unsigned short d_reclen; /* length of this d_name*/
     unsigned char d_type;    /* the type of d_name*/
     char d_name[1];          /* file name (null-terminated)*/
};

關鍵部分就是DIR這個結構體的定義,包括文件描述符、緩衝區偏移、大小、緩衝區內容等,下面定義的就是具體的目錄操做函數了,有打開目錄、讀目錄、重置讀取位置、關閉目錄等,這裏我所須要的就是打開、讀和關閉這三個最基本的目錄操做,下面是使用例子:程序員

#include <stdio.h> 
#include <stdlib.h>    
#include <string.h>  
#include <dirent.h> 

#define MAX_LEN 65535

int main(void) 
{ 
    DIR *dir; 
    struct dirent *ptr; 
    char *flow[MAX_LEN];
    int num = 0, i = 0;
   
    if ((dir=opendir("./data")) == NULL) 
    { 
        perror("Open dir error..."); 
        exit(1);        
    } 
    // readdir() return next enter point of directory dir
    while ((ptr=readdir(dir)) != NULL) 
    { 
        flow[num++] = ptr->d_name;
//      printf("%s\n", flow[num - 1]);
    } 

    for(i = 0; i < num; i++)
    {
        printf("%s\n", flow[i]);
    }
   
    closedir(dir); 
}

運行結果以下:編程

 

一看這結果就不對,輸出的都是同一個文件名(最後一個文件的文件名), 哪裏出了問題呢?將代碼中// printf("%s\n", flow[num - 1]);這行註釋去掉再運行,發現註釋處輸出的是正確的,二者都是輸出的flow數組元素怎麼結果不同呢?通過調試發現是flow[num++] = ptr->d_name;這句代碼的問題,由於這是引用拷貝(地址拷貝),全部的flow元素所有指向同一個對象ptr->d_name,雖然ptr->d_name對象每次的內容不一樣(也就是前面正確輸出的緣由),但全部內容都共享一個地址,用一個簡單的圖說明就是:windows

固然這個問題也比較好解決,也是比較常見的問題,用字符串拷貝或內存拷貝就好了,給flow每一個元素從新申請一塊內存。數組

#include <stdio.h> 
#include <stdlib.h>    
#include <string.h>  
#include <dirent.h> 

#define MAX_LEN 65535

int main(void) 
{ 
    DIR *dir; 
    struct dirent *ptr; 
    char *flow[MAX_LEN];
    int num = 0, i = 0;
   
    if ((dir=opendir("./data")) == NULL) 
    { 
        perror("Open dir error..."); 
        exit(1);        
    } 
    // readdir() return next enter point of directory dir
    while ((ptr=readdir(dir)) != NULL) 
    { 
        flow[num] = (char*)malloc(sizeof(char));
        strcpy(flow[num], ptr->d_name);
        num++;
    } 

    for(i = 0; i < num; i++)
    {
        printf("%s\n", flow[i]);
    }
   
    closedir(dir); 
}

 最終結果就正確了。cookie

2、Windows下遍歷目錄的方法

 在Windows下就比較麻煩了,所要用到的函數都在windows.h中,Windows編程原本就比較繁瑣,下面就不一一介紹所用到的函數了,直接給出封裝的過程。函數

1. 首先模擬Linux下自帶的頭文件dirent.hpost

不一樣的是DIR中去掉了一些不須要的屬性,及只定義了三個我所須要的操做(按需定義)。測試

// dirent.h
#ifndef _SYS_DIRENT_H
#define _SYS_DIRENT_H typedef struct _dirdesc { int dd_fd; /** file descriptor associated with directory */ long dd_loc; /** offset in current buffer */ long dd_size; /** amount of data returned by getdirentries */ char *dd_buf; /** data buffer */ int dd_len; /** size of data buffer */ long dd_seek; /** magic cookie returned by getdirentries */ } DIR; # define __dirfd(dp) ((dp)->dd_fd) DIR *opendir (const char *); struct dirent *readdir (DIR *); void rewinddir (DIR *); int closedir (DIR *); #include <sys/types.h> struct dirent { long d_ino; /* inode number*/ off_t d_off; /* offset to this dirent*/ unsigned short d_reclen; /* length of this d_name*/ unsigned char d_type; /* the type of d_name*/ char d_name[1]; /* file name (null-terminated)*/ }; #endif

 

2. 三個目錄操做函數的實現

固然這是最關鍵的部分,我不知道Linux下是怎麼實現的(找了下沒找到),Windows下實現以下,主要是FindFirstFile()和FindNextFile()這兩個Windows函數,對Windows編程不精,也很差解釋什麼,須要搞明白爲啥這樣實現請上網搜或MSDN。

// dirent.c
#include <stdio.h> #include <windows.h> #include "dirent.h" static HANDLE hFind; DIR *opendir(const char *name) { DIR *dir; WIN32_FIND_DATA FindData; char namebuf[512]; sprintf(namebuf, "%s\\*.*",name); hFind = FindFirstFile(namebuf, &FindData ); if(hFind == INVALID_HANDLE_VALUE) { printf("FindFirstFile failed (%d)\n", GetLastError()); return 0; } dir = (DIR *)malloc(sizeof(DIR)); if(!dir) { printf("DIR memory allocate fail\n"); return 0; } memset(dir, 0, sizeof(DIR)); dir->dd_fd = 0; // simulate return return dir; } struct dirent *readdir(DIR *d) { int i; static struct dirent dirent; BOOL bf; WIN32_FIND_DATA FileData; if(!d) { return 0; } bf = FindNextFile(hFind,&FileData); //fail or end if(!bf) { return 0; } for(i = 0; i < 256; i++) { dirent.d_name[i] = FileData.cFileName[i]; if(FileData.cFileName[i] == '\0') break; } dirent.d_reclen = i; dirent.d_reclen = FileData.nFileSizeLow; //check there is file or directory if(FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { dirent.d_type = 2; } else { dirent.d_type = 1; } return (&dirent); } int closedir(DIR *d) { if(!d) return -1; hFind=0; free(d); return 0; }

 

3. 使用方法

與Linux下使用如出一轍,不須要改動一句代碼就可應用,但卻發現了與Linux下自帶實現一樣的問題,即也是引用拷貝,以下。

 由於這是咱們本身實現的代碼,因此字符串拷貝不是最佳解決方案,修改原實現代碼纔是最好的方法,固然若是是爲了可移植性,就不須要改動了,就用字符串拷貝這樣代碼到Linux下就不須要改動了。下面看如何修改原實現解決:

a. 首先定位問題,能夠很明顯的知道是readdir這個函數的問題;

b. 而後找出問題根源,經過前面的分析可知問題的根源在於每次ptr->d_name使用的是同一內存地址,即ptr地址不變,而ptr是readdir返回的struct dirent指針,因此問題的根源在於readdir返回的dirent結構體地址問題,從上面代碼中能夠看到static struct dirent dirent; 這句代碼,其中dirent的地址就是返回的地址,注意到dirent被定義爲static,你們都知道C中static聲明的變量調用一次後地址就不變了,存在靜態存儲區,也就是每次readdir返回的地址都是不變的,但指向的內容每次都被覆寫,這就是問題所在;

c. 最後解決問題,知道問題根源後,問題就比較容易解決了,就是每次給dirent從新申請內存,看以下個人作法,注意我這裏不能簡單的struct dirent *dirent = (struct dirent *)malloc(sizeof(struct dirent))就結束了,看前面dirent結構體定義中char d_name[1];這裏我只給d_name一個內存空間,顯然不夠,因此也要給它申請內存,我這裏是按需申請內存,若是定義爲char d_name[256];這樣的就不須要了(通常文件名不是太長吧)。

struct dirent *readdir(DIR *d)  
{  
    int i;  
    
    BOOL bf;  
    WIN32_FIND_DATA FileData;  
    if(!d)  
    {  
        return 0;  
    }  
  
    bf=FindNextFile(hFind,&FileData);  
    //fail or end  
    if(!bf)  
    {  
        return 0;  
    }

    struct dirent *dirent = (struct dirent *)malloc(sizeof(struct dirent)+sizeof(FileData.cFileName)); for(i = 0; i < 256; i++)  
    {  
        dirent->d_name[i] = FileData.cFileName[i];  
        if(FileData.cFileName[i] == '\0') break;  
    }  
    dirent->d_reclen = i;  
    dirent->d_reclen = FileData.nFileSizeLow;  
  
    //check there is file or directory  
    if(FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)  
    {  
        dirent->d_type = 2;  
    }  
    else  
    {  
        dirent->d_type = 1;  
    }  
      
    return dirent;
} 

 最終Windows運行結果以下:

PS:不知道這裏你們有沒有注意一個很小的細節,就是輸出的不一樣(用的是一個相同的目錄結構),Linux下輸出了當前目錄.和上層目錄..而Windows下只輸出了上層目錄..,固然這不要緊,由於我要的只是下面的文件名便可。OK,終於完成了,中間找bug花了很多時間,嘿嘿~~~

 

參考資料:

http://blog.csdn.net/lindabell/article/details/8181866

相關文章
相關標籤/搜索