Linux C 講解系統調用readdir, readdir_r 以及如何遍歷目錄下的所有文件


readdir與readdir_r簡要說明

readdir可以用來遍歷指定目錄路徑下的所有文件。不過,不包含子目錄的子文件,如果要遞歸遍歷,可以使用深度遍歷,或者廣度遍歷算法。
readdir_r 是readdir的可重入版本,線程安全。readdir因為直接返回了一個static的struct dirent,因此是非線程安全。

readdir如何遍歷目錄子文件?

1. opendir打開目錄

opendir有2個版本:opendir,fopendir。前者參數為目錄對應字符串,后者參數為目錄對應已打開文件描述符。

#include <sys/types.h>
#include <dirent.h>

DIR *opendir(const char *name);
DIR *fdopendir(int fd);

用法模型:

DIR *dirp;
const char *base_dir = "/home/martin/document";

if ((dirp = opendir(base_dir)) != NULL) {
  perror("opendir error");
  return -1;
}

// 調用readdir遍歷目錄子文件
...

closedir(base_dir);

2. readdir遍歷目錄子文件

readdir需要一個已打開(調用opendir)的DIR對象作為參數。

#include <dirent.h>

struct dirent *readdir(DIR *dirp);

int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);

dirent 結構定義

struct dirent {
   ino_t          d_ino;       /* inode number i節點編號 */
   off_t          d_off;       /* not an offset; see NOTES 早期文件系統中,telldir返回文件在目錄內的偏移 */
   unsigned short d_reclen;    /* length of this record dirent 記錄的實際長度 */
   unsigned char  d_type;      /* type of file; not supported
                                  by all filesystem types 文件類型 */
   char           d_name[256]; /* filename 文件名 */
};

成員介紹:

  • d_ino i節點編號,操作系統用來識別文件的,每個文件都有一個inode number(參見Inode詳解

  • d_off 早期文件系統中,文件系統使用平面表格,telldir返回文件在目錄內的偏移,而d_off就代表這個偏移的緩存。應用把該值當做一個不透明的值即可。

  • d_reclen 記錄的實際長度,並非sizeof(struct dirent)的值,而是sizeof(struct dirent) - sizeof(name) + {strlen(name) 補齊8字節對齊}。

  • d_type 文件類型,並非所有文件系統都支持。另外一種支持更好的查看文件類型的方式是,使用stat系統調用的st_mode。

  • d_name 文件名。在代碼中可能會看到后面有注釋/* We must not include limits.h! */,這是因為POSIX.1時,未規定路徑名長度NAME_MAX,而POSIX.1要求d_name最多支持255個字符,255這個魔法數字是一個難以移植的特定數字。limits.h中定義的NAME_MAX是在POSIX.1-2001加入的。

遍歷子文件模型:

DIR *dirp;
const char *base_dir = "/home/martin/document";

if ((dirp = opendir(base_dir)) != NULL) {
  perror("opendir error");
  return -1;
}

// 調用readdir遍歷目錄子文件
struct dirent *dp;
while ((dp = readdir(dirp)) != NULL) {
  // 讀取、打印文件名、文件類型等信息
  printf dp->d_name, d_type
}

closedir(base_dir);

3. readdir完整示例

要遍歷的目錄:/home/martin/documents
執行$ tree -L 1

執行$ls -al

readdir遍歷指定目錄,並打印示例代碼

int list_file(const char *base_dir)
{
    DIR *dirp;
    struct dirent* dp;

    // 打開目錄
    if ((dirp = opendir(base_dir)) == NULL) {
        perror("opendir error");
        return -1;
    }
    
    printf("sizeof(dirent) = %ld\n", sizeof(struct dirent) - 256);

    printf("%-20s %10s %25s %15s %15s %-s\n", "type", "d_ino", "d_off", "d_reclen", "len", "filename");

    while ((dp = readdir(dirp)) != NULL) {
        // 忽略當前目錄"."和上一級目錄".."(父目錄)
        if (0 == strcmp(dp->d_name, ".") || 0 == strcmp(dp->d_name, ".."))
            continue;

        // 讀取文件類型
        char type[50];
        switch (dp->d_type) {
            case DT_DIR: // a directory
                snprintf(type, sizeof(type), "%s", "directory");
                break;
            case DT_REG: // a regular file
                snprintf(type, sizeof(type), "%s", "regular file");
                break;
            case DT_BLK: // a block device
                snprintf(type, sizeof(type), "%s", "block device");
                break;
            case DT_CHR: // a character device
                snprintf(type, sizeof(type), "%s", "character device");
                break;
            case DT_FIFO: // a named pipe (FIFO)
                snprintf(type, sizeof(type), "%s", "named pipe (FIFO)");
                break;
            case DT_LNK: // a symbolic link
                snprintf(type, sizeof(type), "%s", "symbolic link");
                break;
            case DT_SOCK: // a UNIX domain socket
                snprintf(type, sizeof(type), "%s", "UNIX domain socket");
                break;
            default: // DT_UNKNOWN - file type unknown
                snprintf(type, sizeof(type), "%s", "file type unknown");
                break;
        }
        printf("%-20s %10lu %25ld %15u %15ld %-s\n", type, dp->d_ino, dp->d_off, dp->d_reclen, strlen(dp->d_name), dp->d_name);
    }

    // 關閉目錄
    closedir(dirp);
    return 0;
}

int main(int argc, char *argv[])
{
    list_file("/home/martin/Documents");
    return 0;
}

運行結果

sizeof(dirent) = 24
type                      d_ino                     d_off        d_reclen             len filename
regular file             292970       3024556464499973779              40              13 list12.dKLpEX
directory               1180001       3470762240268728258              32               8 winshark
regular file             293625       4800707232840484128              32               8 list.txt
regular file             292872       9057525276248794967              64              37 RFC959_FTP傳輸協議(中文版).pdf
regular file             288738       9223372036854775807              40              14 rfc114.txt.pdf

4. readdir_r完整示例

readdir_r遍歷指定目錄,並打印示例代碼

#define NAME_MAX_PORTABLE     // 文件名稱可移植

int list_file_r(const char *base_dir)
{
    DIR *dirp;

#ifndef NAME_MAX_PORTABLE // 未定義 文件名稱可移植
    struct dirent d1, d2;
    struct dirent *entryp, *res;
    entryp = &d1;
    res = &d2;
#else // 定義了 文件名稱可移植
    struct dirent *entryp, *res;
    long name_max = pathconf(base_dir, _PC_NAME_MAX);

    if (name_max == -1) {
        name_max = 255; // guess
    }
    int len = offsetof(struct dirent, d_name) + name_max + 1; // + 1 for NULL terminate byte
    entryp = malloc(len);
    res = malloc(len);
#endif

    // 打開目錄
    if ((dirp = opendir(base_dir)) == NULL) {
        perror("opendir error");
        return -1;
    }

    printf("%-20s %10s %25s %15s %15s %-s\n", "type", "d_ino", "d_off", "d_reclen", "len", "filename");
    int ret;
    while(1) {
        if ((ret = readdir_r(dirp, entryp, &res)) > 0) {
            fprintf(stderr, "readdir_r error: %d\n", ret);
            perror("readdir_r error");
            return -1;
        }

        if (res == NULL)
            break;
        if (strcmp(entryp->d_name, ".") == 0 || strcmp(entryp->d_name, "..") == 0)
            continue;

        // 忽略當前目錄"."和上一級目錄".."(父目錄)
        if (0 == strcmp(entryp->d_name, ".") || 0 == strcmp(entryp->d_name, ".."))
            continue;
        // 讀取文件類型
        char type[50];
        switch (entryp->d_type) {
            case DT_DIR: // a directory
                snprintf(type, sizeof(type), "%s", "directory");
                break;
            case DT_REG: // a regular file
                snprintf(type, sizeof(type), "%s", "regular file");
                break;
            case DT_BLK: // a block device
                snprintf(type, sizeof(type), "%s", "block device");
                break;
            case DT_CHR: // a character device
                snprintf(type, sizeof(type), "%s", "character device");
                break;
            case DT_FIFO: // a named pipe (FIFO)
                snprintf(type, sizeof(type), "%s", "named pipe (FIFO)");
                break;
            case DT_LNK: // a symbolic link
                snprintf(type, sizeof(type), "%s", "symbolic link");
                break;
            case DT_SOCK: // a UNIX domain socket
                snprintf(type, sizeof(type), "%s", "UNIX domain socket");
                break;
            default: // DT_UNKNOWN - file type unknown
                snprintf(type, sizeof(type), "%s", "file type unknown");
                break;
        }
        printf("%-20s %10lu %25ld %15u %15ld %-s\n", type, entryp->d_ino, entryp->d_off, entryp->d_reclen, strlen(entryp->d_name), entryp->d_name);
    }

    printf("exit list_file_r\n");

#ifdef NAME_MAX_PORTABLE
    // 釋放資源
    free(res);
    free(entryp);
#endif

    // 關閉目錄
    closedir(dirp);
    return 0;
}

int main(int argc, char *argv[])
{
    list_file_r("/home/martin/Documents");
    return 0;
}

運行結果

type                      d_ino                     d_off        d_reclen             len filename
regular file             292970       3024556464499973779              40              13 list12.dKLpEX
directory               1180001       3470762240268728258              32               8 winshark
regular file             293625       4800707232840484128              32               8 list.txt
regular file             292872       9057525276248794967              64              37 RFC959_FTP傳輸協議(中文版).pdf
regular file             288738       9223372036854775807              40              14 rfc114.txt.pdf
exit list_file_r


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM