UNIX 檔案系統基本操作

helloxchen發表於2010-10-21

UNIX 檔案系統基本操作

為娛樂和利益而讀取目錄

Chris Herborth, 技術作家兼軟體開發人員, 作家
Chris Herborth 的照片
Chris Herborth 是一位屢獲殊榮的高階技術作者,十餘年來撰寫了許多篇作業系統和程式設計方面的文章。在陪伴兒子 Alex 或妻子 Lynette 之餘,他設計、編寫和研究(也就是玩)影片遊戲。

簡介: 利用 readdir()stat() 函式瀏覽目錄中的條目。因為 UNIX® 系統中存在大量的檔案和目錄,所以您需要了解如何使用 readdir() 函式處理這些目錄條目,以及如何使用 stat() 函式提取這些條目的相關資訊。在您的 UNIX 程式開發工作中,這些基礎的檔案系統操作可以很好地為您提供服務,讓您可以輕鬆地發現並讀取 UNIX 系統中檔案、目錄和符號連結。

UNIX® 中任何事物都是檔案 的觀點意味著,您將始終會與檔案和目錄打交道,無論您開發的是何種型別的應用程式。任何事物都儲存為檔案,從資料到配置檔案、甚至是裝置,在對 UNIX 程式設計經過幾個小時的學習之後,stdio.h 系統 Header 中的函式將能夠為您提供很好的幫助。

一個時常困擾 UNIX 程式設計新手的問題是,如何瀏覽一個目錄,並對其中的檔案、目錄和符號連結進行相應的處理。如何能夠獲取它們的列表,以及如何能夠確定它們究竟是什麼?

請繼續閱讀本文,以學習如何使用 dirent.h 函式系列 (opendir()/readdir()/closedir()) 來讀取目錄中的條目,以及使用 stat() 函式來確定這些條目所對應的內容。

對於一個給定路徑的目錄,應該如何讀取其中的條目呢?您無法像操作檔案那樣開啟目錄(使用 open()fopen() 函式),並且即便可以這樣做,所得到的資料可能是您正在使用的檔案系統的專用格式,而對於不十分熟悉的程式設計師來說,直接訪問這些資料將使情況變得更糟。

dirent.h 函式,opendir()readdir()closedir(),它們正是您所需要的。這些函式的使用與用來對檔案進行操作的 open/read/close 的習慣用法非常相似,但有一點除外:對於每個目錄條目,readdir() 函式一次返回一個指向特殊結構(struct dirent 型別)的指標。通常,對目錄進行瀏覽類似於清單 1 中所示的虛擬碼。



                
dir = opendir( "some/path/name" )
entry = readdir( dir )
while entry is not NULL:
    do_something_with( entry )
    entry = readdir( dir )
closedir( dir )

在出現問題時,opendir()readdir() 函式都會返回 NULL,並且將設定全域性變數 errno 的值,以指出所出現的錯誤。如果 readdir() 返回 NULL,並且 errno 為 0(有時也稱為 EOK 或 ENOERROR),則表示沒有其他的目錄條目。

有一點需要注意,每個目錄都包含“.”(對該目錄的引用)和“..”(對該目錄的父目錄的引用)條目。根據您所進行的操作,可能需要忽略對這些條目的處理。

請注意,readdir() 不是執行緒安全的,因為所返回的結構是儲存在函式庫中的一個靜態變數。大多數現代的 UNIX 系統都具有執行緒安全的 readdir_r(),如果您正在編寫執行緒程式碼,可以使用這個函式作為替代。

POSIX 1003.1 標準僅僅為 struct dirent 定義了一個必需的條目,即 char 陣列 d_name。這是用標準的以 NULL 結尾的字串表示的該條目的名稱。這個結構中任何其他內容都是特定於您的 UNIX 系統的。

的確如此,struct dirent 中其他所有內容 都是不可移植的。嚴格滿足一致性的系統不應該在其中包含任何其他的內容。如果您編寫了使用額外結構成員的程式碼,那麼您必須將其標記為不可移植的,並且包含一個完成相同任務的替換程式碼路徑,如果您認為這樣做特別友好的話。

例如,許多 UNIX 包含一個 d_type 成員和一些附加常量,這樣一來,您無需額外的 stat() 呼叫就可以檢查目錄條目的型別。除了減少另外的系統呼叫之外,這種不可移植的擴充套件還減少了從檔案系統獲取更多後設資料的開銷非常高的訪問操作。眾所周知,在大多數 UNIX 上,stat() 函式的執行速度非常慢。

除了獲取目錄中條目的名稱之外,您可能還需要一些附加資訊,以確定下一步要進行的操作。至少,僅根據目錄條目的名稱,您無法辨別檔案條目。

stat() 函式會將特定檔案的相關資訊填入 struct stat 結構中,如果您獲得的是檔案描述符而不是檔名,那麼作為替代,您可以使用 fstat() 函式。如果您想能夠檢測出符號連結,那麼可以對檔名使用 lstat()

readdir() 返回的 struct dirent 不同,struct stat 具有相當多的標準的、必需的成員:

  • st_mode——檔案許可權(使用者、其他使用者、組)和標誌
  • st_ino——檔案序列號
  • st_dev——檔案裝置號
  • st_nlink——檔案連線計數
  • st_uid——所有者使用者 ID
  • st_gid——所有者組 ID
  • st_size——以位元組表示的檔案大小(針對普通檔案)
  • st_atime——最後的訪問時間
  • st_mtime——最後的修改時間
  • st_ctime——檔案的建立時間

st_mode 成員使用 S_*() 宏,這樣就可以找出您所處理的目錄條目的型別:

  • S_ISBLK(mode)——是否為塊特殊檔案?(通常是某種基於塊的裝置)
  • S_ISCHR(mode)——是否為字元特殊檔案?(通常是某種基於字元的裝置)
  • S_ISDIR(mode)——是否為目錄?
  • S_ISFIFO(mode)——是否為管道或 FIFO 特殊檔案?
  • S_ISLNK(mode)——是否為符號連結?
  • S_ISREG(mode)——是否為普通檔案?

眾所周知,在大多數檔案系統上,stat() 函式的執行速度非常慢,所以如果您打算在將來再次使用該資訊,可能需要對其進行快取。

通常,您並不關心符號連結。如果對符號連結呼叫 stat(),那麼您將獲取該連結所指向的檔案的相關資訊。這和使用者的體驗是一致的,因為控制與該檔案互動的是目標檔案的許可權,而不是符號連結本身。

有些應用程式,如 ls 和備份程式,需要能夠顯示連結檔案本身的相關資訊,例如它所指向的檔案。當您使用 lstat() 來代替 stat() 時,以及當您出於特定的目的而需要獲取符號連結本身的相關資訊,而不是直接與其連結的檔案打交道時,情況也是這樣的。

既然已經學習瞭如何使用 readdir()stat() 來查詢目錄中的條目,那麼讓我們來看看演示這些函式的一些實際程式碼。

這裡所介紹的程式碼將瀏覽命令列中指定的一個或多個目錄,並顯示在該目錄中找到的每個條目的相關資訊。當它找到另一個目錄時,它會對該目錄進行同樣的處理。對於符號連結,將顯示其目標檔案,並且還將顯示普通檔案的大小。將忽略特殊檔案。

清單 2 所示,這個簡單的演示應用程式中包含了各種 Header 檔案。程式的開始塊中包含了大多數程式中使用的標準部分,並且後面的四項是在該程式中使用 readdir()stat() 所必需的。



                
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

process_directory() 函式(開始於清單 3,結束於清單 6)讀取了指定的目錄,並顯示了每個條目的相關資訊。opendir() 返回的 DIR 指標與 fopen() 返回的 FILE 指標類似,它是一個用於跟蹤目錄流的作業系統特定的物件,您應該忽略其具體內容。



                
unsigned process_directory( char *theDir )
{
    DIR *dir = NULL;
    struct dirent entry;
    struct dirent *entryPtr = NULL;
    int retval = 0;
    unsigned count = 0;
    char pathName[PATH_MAX + 1];

    /* Open the given directory, if you can. */  
    dir = opendir( theDir );
    if( dir == NULL ) {
        printf( "Error opening %s: %s", theDir, strerror( errno ) );
        return 0;
    }

在開啟了指定的目錄之後,呼叫 readdir_r()(請參見清單 4)以獲取關於第一個條目的資訊,隨後每次呼叫 readdir_r() 都將返回下一個條目,直到到達了目錄末尾,並且 entryPtr 被設定為 NULL。這裡還使用了 strncmp() 來檢查“.”和“..”條目,以便略過它們。如果不略過它們,您將永遠都在處理類似“theDir/./././././././././.”等這樣的目錄。



                
    retval = readdir_r( dir, &entry, &entryPtr );
    while( entryPtr != NULL ) {
        struct stat entryInfo;
        
        if( ( strncmp( entry.d_name, ".", PATH_MAX ) == 0 ) ||
            ( strncmp( entry.d_name, "..", PATH_MAX ) == 0 ) ) {
            /* Short-circuit the . and .. entries. */
            retval = readdir_r( dir, &entry, &entryPtr );
            continue;
        }

既然已經得到了目錄的條目名稱,那麼您需要構造一個更加完整的路徑(請參見清單 5),然後呼叫 lstat() 以獲取該條目的相關資訊。因為符號連結需要特殊的處理,所以這裡使用了 lstat() 函式。您可以使用 readlink() 函式找到其目標檔案。

如果該條目是一個目錄,那麼對這個目錄遞迴地呼叫 process_directory(),並將其中所找到的條目數加到執行總數中。如果該條目是一個檔案,那麼顯示其名稱和位元組數(可在 struct statst_size 成員中找到)。



                
        (void)strncpy( pathName, theDir, PATH_MAX );
        (void)strncat( pathName, "/", PATH_MAX );
        (void)strncat( pathName, entry.d_name, PATH_MAX );
        
        if( lstat( pathName, &entryInfo ) == 0 ) {
            /* stat() succeeded, let's party */
            count++;
            
            if( S_ISDIR( entryInfo.st_mode ) ) {            
/* directory */
                printf( "processing %s/n", pathName );
                count += process_directory( pathName );
            } else if( S_ISREG( entryInfo.st_mode ) ) { 
/* regular file */
                printf( "t%s has %lld bytesn",
                    pathName, (long long)entryInfo.st_size );
            } else if( S_ISLNK( entryInfo.st_mode ) ) { 
/* symbolic link */
                char targetName[PATH_MAX + 1];
                if( readlink( pathName, targetName, PATH_MAX ) != -1 ) {
                    printf( "t%s -> %sn", pathName, targetName );
                } else {
                    printf( "t%s -> (invalid symbolic link!)n",
 pathName );
                }
            }
        } else {
            printf( "Error statting %s: %sn", pathName, strerror( 
errno ) );
        }

while 迴圈的底部,讀取另一個目錄條目並對其進行處理。如果您完成了對目錄條目的處理,那麼關閉當前開啟的目錄,並返回經過處理的條目的數目。



                
        retval = readdir_r( dir, &entry, &entryPtr );
    }
    
    /* Close the directory and return the number of entries. */
    (void)closedir( dir );
    return count;
}

最後,清單 7 顯示了該程式的 main() 函式,它只是對命令列中傳遞的每個引數呼叫了 process_directory() 函式。一個真正的程式應該具有使用方法訊息,並且在使用者沒有指定任何引數時,提供某種形式的反饋資訊,但我把這項內容作為練習留給讀者。



                
/* readdir_demo main()
 * 
 * Run through the specified directories, and pass them
 * to process_directory().
 */
int main( int argc, char **argv )
{
    int idx = 0;
    unsigned count = 0;
    
    for( idx = 1; idx < argc; idx++ ) {
        count += process_directory( argv[idx] );
    }
    
    return EXIT_SUCCESS;
}

這就是整個程式。儘管包含了較多的檔案,但處理目錄條目並不是十分困難。

使用 readdir()stat() 函式瀏覽目錄中的條目並確定對其進行的額外處理,是非常簡單的,在您需要列舉目錄中的內容時,也可能會使用到這種處理方法。它是一種很實用的方法,但是對於一些沒有經驗的 UNIX 開發人員來說,卻難以掌握。本文的目的是降低其難度,使得 UNIX 開發人員能夠充分利用這些有價值的函式

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24790158/viewspace-1040153/,如需轉載,請註明出處,否則將追究法律責任。

相關文章