【C/C++】資料夾的開啟、遍歷、刪除、建立、關閉操作彙總
轉自:http://sodino.com/2015/03/09/c-directory-io/
需要用到的標頭檔案為:#include <sys/types.h>
#include <dirent.h>
開啟資料夾
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
opendir()
函式開啟一個指定路徑name
的資料夾關聯的流,並將該流以執行結果的方式返回給呼叫者。在預設情況下,該流指向資料夾下的第一個目錄。fdopendir()
函式與opendir()
是相同的,只是該函式接收的方式是檔案描述符fd
。fd
可以通過執行函式int dirfd(DIR *dirp);
fd
做為引數傳遞給fdopendir()
之後,則該fd
就不要在程式中的其它地方使用了。以上兩個函式正常執行時都會返回與資料夾相關聯的流;否則返回NULL,錯誤的原因可以從
errno
中獲得。
struct dirent *readdir(DIR *dirp); NO Thread-Safe
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result); Thread-Safe
執行readdir()
函式將返回資料夾流dirp
中的下一個條目,即資料夾下的子目錄,重複執行時將會逐一返回資料夾下所有的子目錄;一旦返回NULL則表示已經把所有的子目錄都遍歷過了或者出現了錯誤(可以通過errno
返回的結果是這樣的一個結構體:
1234567 |
struct dirent { ino_t d_ino; /* 在檔案系統中的inode number */ off_t d_seekoff; /* 與資料夾流的位置指標操作相關 */ unsigned short d_reclen; /* 本記錄的資料長度 */ unsigned char d_type; /* 當前遍歷子項的檔案型別:檔案、資料夾、link、socket等 */ char d_name[256]; /* 當前遍歷子項的檔名 */ |
以上結構體出自 man ,在不同系統上會有各自不同的差別。
在上面的結構體中,檔名d_name
的限制長度256是包括字串終止符’\0’的,所以在OS中,資料夾的命名最大字元數是255個。(在Mac中嘗試是無法輸入第256個字元的)
結構體中的d_type
用於標識當前的資料夾型別,支援的型別有:
12345678 | DT_BLK This is a block device.DT_CHR This is a character device.DT_DIR 資料夾 This is a directory.DT_FIFO This is a named pipe (FIFO).DT_LNK 符號連結檔案 This is a symbolic link.DT_REG 檔案 This is a regular file. DT_SOCK SOCKET連結 This is a UNIX domain socket.DT_UNKNOWN The file type is unknown. |
readdir_r()
是readdir()
的可重入實現版本,功能是一致的,但readdir_r()
是執行緒安全的。
執行readdir_r()
時遍歷得到的子目錄資料會存放在第二個引數entry
中,第三個引數result
指標指向子目錄的entry
,且函式返回值返回0。當遍歷到資料夾結束的位置時,返回值仍返回0,但第三個引數result
指標指向NULL。當函式執行錯誤時,則返回一個大於0的數字(即EBADF
)。
long telldir(DIR *dirp);
void seekdir(DIR *dirp, long loc);
void rewinddir(DIR *dirp);
DIR也是流的一種,對其進行讀操作時也有位置指標的概念及操作。telldir()
可以獲取當前的位置指標位置。返回值表示當前遍歷的子目錄是當前資料夾下的第幾個目錄(第0個是”.”當前目錄,第1個是”..”上一級目錄,所以從第2個開始計數。)seekdir()
可以操作位置指標跳轉到指定的子目錄序號loc
。rewinddir()
可以將位置指標恢復到流的起始位置。
int scandir(const char *dirp, struct dirent ***namelist,
int (*filter)(const struct dirent *),
int (*compar)(const struct dirent **, const struct dirent **));int scandirat(int dirfd, const char *dirp, struct dirent ***namelist,
int (*filter)(const struct dirent *),
int (*compar)(const struct dirent **, const struct dirent **));
scandir()
是遍歷資料夾的另一種方式。本函式支援自定義遍歷的過濾條件及結果排序,封閉了遍歷的實現,執行後直接提供合法的目錄資訊,但該資訊也只是目錄名稱而已。函式的返回結果是合法的子目錄的個數;當函式執行出現錯誤時則返回-1且可以通過errno
去錯誤詳情。dirp
指定要遍歷的資料夾路徑。namelist
用於指向遍歷結果。可以看出該指標指向一個二維的字元陣列,該二維陣列儲存著資料夾下的所有合法的檔名。*filter
是一個函式指標,該函式有一個引數const struct dirent *
是指在遍歷過程中所遍歷到的每一個子目錄dirent
,filter
可以根據dirent
的型別、名稱等資訊來判定當前的dirent
是否為合法的子目錄,合法則函式返回0,則該子目錄的名稱會被儲存在namelist
中;否則返回非0,則該子目錄被過濾掉。*compar
也是一個函式指標,該函式的兩個引數都是const struct dirent *
用於決定兩個子目錄在namelist
中的順序。
C的庫中也預設提供了兩個檔名稱排序的方法:
int alphasort(const struct dirent **a, const struct dirent **b);
int versionsort(const struct dirent **a, const struct dirent **b);
alphasort()
的實現是把dirent
的名稱用strcoll()
進行比較,排序的結果是按ASCII編碼的值由小到大排序。versionsort()
的實現是把dirent
的名稱strverscmp()
進行比較,排序的結果是也按ASCII編碼的值由小到大排序,不同的是支援對名稱中按數字序號的排序。舉個例子對比alphasort()
與versionsort()
的不同:
alphasort: pc1 pc10 pc2 … pc9
versionsort: pc1 pc2 … pc9 pc10
很明顯,versionsort()
的排序結果更符合普通人的閱讀習慣。
scandirat()
是scandirat()
的擴充套件函式,主要區別就在於第一個引數dirfd
與第二個引數dirp
。
如果dirp
是一個絕對路徑的字串,則函式無視引數dirfd
,功能與scandir()
一致。
如果dirp
是一個相對路徑的字串,且引數dirfd
的值是AT_FDCWD (#include <fcntl.h>)
,則表示從應用程式當前的工作路徑拼接上dirp
的相對路徑所組成的絕對路徑下去遍歷;當dirfd
值不為AT_FDCWD
時而是一個代替資料夾的file
descriptor
(參考dirfd(DIR*)
)時,則遍歷的資料夾路徑即為dirfd
所指向的資料夾再拼接上dirp
的相對路徑所組成的絕對路徑。
下面的示例程式碼演示了readdir
readdir_r
scandir
三種方式遍歷資料夾的實現。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 | #include <stdio.h>#include <errno.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <dirent.h>#include <sys/stat.h>static void printFileType(int type) { // 子檔案 型別 printf("type:%x ", type); switch (type) { case DT_FIFO: printf(" fifo."); break; case DT_CHR: printf(" character."); break; case DT_DIR: printf(" directory."); break; case DT_BLK: printf(" block."); break; case DT_REG: printf(" regular file."); break; case DT_LNK: printf(" link."); break; case DT_SOCK: printf(" socket."); break; case DT_WHT: printf(" WHT."); break; case DT_UNKNOWN: default: printf(" unknown."); break; }}static void print_dir(const char * dirPath, DIR * dir) { struct dirent * file; // 遍歷資料夾下的內容 while ((file = readdir(dir)) != NULL) { printf("dir position=%ld ", telldir(dir)); // 排隊掉"."(當前目錄)和".."(上一級),無用的資訊 if(strcmp(file->d_name, ".") == 0 || strcmp(file->d_name, "..") == 0) { printf("ignore:%s \n", file->d_name); continue; } // 子檔案 檔名 printf("sub file:%20s ", file->d_name); // 子檔案 inode節點資訊 printf("inode:%llx ", file->d_ino); printf("off:%llx ", file->d_seekoff); // 和流的位置指標相關 printFileType(file->d_type); printf("\n"); }}static void print_dir_r(DIR * dir) { struct dirent entry; struct dirent * result = &entry; while(1){ printf("dir position=%ld ", telldir(dir)); int res = readdir_r(dir, result, &result); if (res == 0){ if (result == NULL) { break; } else { printf("sub file:%20s ", entry.d_name); printFileType(entry.d_type); printf("\n"); } } else { printf("readdir_r() fail:%s \n", strerror(errno)); break; } } printf("\n");}/**過濾掉的“.”與".."*/static int filterDot(const struct dirent * dir) { if (strcmp(dir->d_name,".") == 0 || strcmp(dir->d_name, "..") == 0) { // 過濾掉 "."和".." return 0; } else { return 1; }}static void print_scan_dir(const char * path) { // 指向字元的二維陣列,該陣列儲存著資料夾下的合法子目錄名 struct dirent **namelist; int n; n = scandir(path, &namelist, filterDot, alphasort); printf("after scandir, entry num:%d \n", n); if (n < 0) { perror("scandir"); } else { int idx = 0; while (idx !=n) { printf("idx=%d suf file:%20s \n", idx, namelist[idx]->d_name); // 釋放掉namelist free(namelist[idx]); idx ++; } // 釋放掉namelist free(namelist); }}int main(int argc, char ** argv) { // 指定的一個資料夾路徑 char * dirPath = "/Users/sodino/workspace/xcode/CString/CString/test"; // 開啟資料夾 DIR* dir = opendir(dirPath); print_dir(dirPath, dir); // 重置資料夾位置指標至流的起始位置,以便後續再次遍歷 rewinddir(dir); printf("\n\ndo rewinddir() and go on to list entry(s)\n\n"); print_dir_r(dir); // 重置資料夾位置指標至流的起始位置,以便後續再次遍歷 rewinddir(dir); printf("\n\n do scandir() \n"); print_scan_dir(dirPath); // 關閉 closedir(dir); return EXIT_SUCCESS;} |
效果如下圖:
資料夾關閉
int closedir(DIR *dirp);
closedir()
將關閉一個與資料夾相關聯的流。執行本函式後,dirp
將不可再被使用。
刪除資料夾
int rmdir(const char *pathname);
刪除指定路徑下的資料夾。如果刪除成功,則返回0;否則返回-1,並可通過errno
檢視失敗原因。
建立資料夾
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int dirfd, const char *pathname, mode_t mode);
mkdir()
函式用於建立資料夾,引數pathname
指定要建立的資料夾的絕對路徑。引數mode
用於限定資料夾的檔案許可權(組)。該mode
將會OR操作後合成struct stat
中的st_mode
(詳情)。檔案許可權(組)的mode
具體定義見下文的File
Mode Bits(懶得翻譯了,直接上英文)。所設定的檔案許可權(組)可以通過命令ls -l -a
檢視。
如果函式正常執行並建立資料夾成功,則返回0;否則返回-1並且可以通過errno
檢視錯誤詳情。
擴充套件:直接mkdir(path, S_IRWXU| S_IRWXG| S_IRWXO),可生成的資料夾許可權組是
drwxr-xr-x
而不是drwxrwxrwx
?
原因是:mkdir()
函式中指定的引數mode
值會和當前程式的umask
處理一下,具體處理為mode & ~unmask & 0777
才是最終的資料夾許可權組。檢視當前使用者或程式的umask
可以在命令列終端上輸入命令umask
可看。一般使用者的umask
值為0022
。
mkdirat()
函式是mkdir()
的擴充套件,引數中dirfd
與pathname
的用法跟 scandir()
與scandirat()
的用法是一致的,這裡就不重複說明了。
S_IRWXU read, write, execute/search by owner
S_IRUSR read permission, owner
S_IWUSR write permission, owner
S_IXUSR execute/search permission, owner
S_IRWXG read, write, execute/search by group
S_IRGRP read permission, group
S_IWGRP write permission, group
S_IXGRP execute/search permission, group
S_IRWXO read, write, execute/search by others
S_IROTH read permission, others
S_IWOTH write permission, others
S_IXOTH execute/search permission, others
S_ISUID set-user-ID on execution
S_ISGID set-group-ID on execution
S_ISVTX on directories, restricted deletion flag
The bits defined by S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH, S_ISUID, S_ISGID and S_ISVTX are unique.
S_IRWXU is the bitwise OR of S_IRUSR, S_IWUSR and S_IXUSR.
S_IRWXG is the bitwise OR of S_IRGRP, S_IWGRP and S_IXGRP.
S_IRWXO is the bitwise OR of S_IROTH, S_IWOTH and S_IXOTH.