目錄操作

fowind發表於2024-04-07

目錄相關操作

獲取當前工作目錄getcwd

getcwd函式:獲取當前工作目錄的絕對路徑

// manual
NAME
       getcwd, getwd, get_current_dir_name - get current working directory

SYNOPSIS
       #include <unistd.h>
    
       char *getcwd(char *buf, size_t size);

RETURN VALUE
On success, return a pointer to a string containing the pathname of the current working directory.
On failure, return NULL, and errno is set to indicate the error.

getcwd函式的慣用法

getcwd(NULL,0); 由核心申請合適的記憶體空間,儲存當前工作目錄的絕對路徑
man手冊中說明,使用這種方法時,需要呼叫者free記憶體空間

實現

#include <func.h>

int main(int argc, char* argv[])
{
    // ./getcwd
    // 檢查引數
    if (argc != 1){
        error(1, 0, "Usage: %s path\n", argv[0]);
    }

    //獲取當前工作目錄
    char *cwd = getcwd(NULL, 0);
    if (!cwd) {
        error(1, erron, "getcwd %s", argv[1]);
    }
    
    puts(cwd);
    
    free(cwd);
    return 0;
]

改變當前的工作目錄chdir

chdir函式:改變程序當前的工作目錄

// manual
NAME
       chdir, fchdir - change working directory

SYNOPSIS
       #include <unistd.h>

       int chdir(const char *path);

RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

實現

int main(int argc, char* argv[])
{
    // ./chdir
    // 修改程序的當前目錄
    if (argc != 2) {
        error(1, 0, "Usage: %s path\n", argv[0]);
    }
    
    // 獲取當前目錄
    char *cwd = getcwd(NULL, 0);
    puts(cwd);

    // 修改目錄許可權
    int err = chdir(argv[1]);
    if (err){
        error(1, errno, "chdir");
    }

    cwd = getcwd(NULL, 0);
    puts(cwd);

    return 0;
}

建立目錄mkdir

// manual
NAME
       mkdir, mkdirat - create a directory

SYNOPSIS
       #include <sys/stat.h>
       #include <sys/types.h>

       int mkdir(const char *pathname, mode_t mode);
RETURN VALUE
       return zero on success, or -1 if an error occurred (in which  case,  errno is set appropriately).

其中mode表示許可權

0777: 目錄的基本許可權
0666: 檔案的基本許可權

實現

int main(int argc, char* argv[])
{
    // ./mkdir pathname [mode]
    // 建立目錄
    if (argc != 3) {
        error(1, 0, "Usage: %s pathname [mode]\n", argv[0]);
    }
    
    // 引數解析
    mode_t mode;
    sscanf(argv[1], "%o", mode);

    // 建立目錄
    int err = mkdir(argv[1], mode);
    if (err){
        error(1, errno, "mkdir %s\n", argv[1]);
    }

    return 0;
}

刪除空目錄rmdir

// manual
NAME
       rmdir - delete a directory

SYNOPSIS
       #include <unistd.h>

       int rmdir(const char *pathname);
RETURN VALUE
       On  success, zero is returned.  On error, -1 is returned, and errno is set appro‐priately.

實現

int main(int argc, char* argv[])
{
    // ./rmdir pathname
    // 刪除空目錄
    if (argc != 2) {
        error(1, 0, "Usage: %s pathname\n", argv[0]);
    }
    
    int err = rmdir(argv[1]);
    if (err) {
        error(1, errno, "rmdir %s\n", argv[1]);
    }
 
    return 0;
}

讀取格式化輸入 sscanf

sscanf函式可以將字串以特定格式讀入,可用於將字串以特定格式寫入到某個變數中

int sscanf(const char *str, const char *format, ...);
sscanf(argv[1], "%o", mode);

錯誤處理函式error

目錄流相關操作

使用目錄流,可以使開發人員在程式中有效訪問和操作檔案系統給的目錄結構

目錄流的基本單位是目錄項

以下是目錄流常見的功能:

  • opendir: 開啟一個目錄

  • closedir:關閉一個目錄流

  • readdir: 讀取下一個目錄項

基本用不到系列:

  • telldir:返回目錄流的當前位置

  • seekdir:移動到某個為止

  • rewinddir:移動到第一個

目錄項

目錄流是由一個個的目錄項組成的,目錄項的結構體如下:

struct dirent {
    ino_t          d_ino;       // inode number
    off_t          d_off;       // offset to the next dirent
    unsigned short d_reclen;    // length of this record
    unsigned char  d_type;      // type of file
    char           d_name[256]; // filename
};

在目錄項中,至少要包含:d_ino d_name d_type

目錄項的d_type

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     This is a UNIX domain socket. 
DT_UNKNOWN  The file type could not be determined.

開啟目錄流opendir

// manual
NAME
       opendir, fdopendir - open a directory

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

       DIR *opendir(const char *name);
RETURN VALUE
       The opendir() functions return a pointer to the directory stream.
       On error, NULL is returned, and errno is set appropriately.

關閉目錄流closedir

// manual
NAME
       closedir - close a directory

SYNOPSIS
       #include <sys/types.h>

       #include <dirent.h>

       int closedir(DIR *dirp);

RETURN VALUE
       The closedir() function returns 0 on success.  On error, -1 is returned, and  errno is set appropriately.

讀取目錄項readdir

readdir函式:用於讀取一個目錄項,並返回下一個目錄項的dirp,直到為NULL或發生錯誤

NAME
       readdir - read a directory

SYNOPSIS
       #include <dirent.h>

       struct dirent *readdir(DIR *dirp);

DESCRIPTION
       The  readdir()  function returns a pointer to a dirent structure representing the
       next directory entry in the directory stream pointed to by dirp.  It returns NULL
       on reaching the end of the directory stream or if an error occurred.
           
RETURN VALUE
       On  success,  readdir() returns a pointer to a dirent structure.  (This structure
       may be statically allocated; do not attempt to free(3) it.)

       If the end of the directory stream is reached, NULL is returned and errno is  not
       changed.   If  an  error occurs, NULL is returned and errno is set appropriately.
       To distinguish end of stream from an error, set  errno  to  zero  before  calling
       readdir() and then check the value of errno if NULL is returned.

根據manual中描述,在使用readdir函式讀取 direction entry 結束時,需要判斷是否發生錯誤而導致退出

readdir函式的一個慣用法

errno = 0;
struct dirent *pcurr;
while ((pcurr = readdir(dirp)) != NULL) {
    // 具體操作...
}
if (errno) {
    error(1, errno, "readdir");
}

刪除目錄項

在Linux系統中,刪除檔案實際上是將硬連結數減1,可以使用unlink函式

// manual
NAME
       unlink - delete a name and possibly the file it refers to

SYNOPSIS
       #include <unistd.h>

       int unlink(const char *pathname);

RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is set
       appropriately.

目錄和目錄流使用場景

Linux系統中,一切皆檔案。因此,目錄可以看作是一種特殊的檔案。它的內容是目錄項的集合

如何正確使用針對目錄和目錄流的操作,就顯得尤為重要。

目錄操作:當涉及到對目錄的增、刪、查、改時使用。

目錄流操作:遍歷目錄流時使用。

遞迴處理目錄(重點)

遞迴處理目錄時常用步驟

1. 開啟目錄流(opendir)
2. 遍歷目錄流
3. 關閉目錄流

簡易版的tree

實現tree類似於樹的深度優先搜尋。

需要注意的是,在實現青春版的tree時,需要忽略...,否則會導致死迴圈。

#include <func.h>

void dfs_print(const char *path, int width);
int directories, files;

int main(int argc, char* argv[])
{
    // ./tree dir
    if (argc != 2) {
        error(1, 0, "Usage: %s dir\n", argv[1]);
    }
    // 1. 列印目錄資訊
    puts(argv[1]);
    // 2. 遍歷目錄流, 遞迴列印每一個目錄項
    dfs_print(argv[1], 4);
    // 3. 列印統計資訊
    printf("%d directories, %d files\n", directories, files);
    return 0;
}

void dfs_print(char *path, int depths) {
    // 1. 開啟檔案流
    DIR *pdir = opendir(path);
    if (pdir == NULL) {
        error(1, erron, "opendir directory");
    }

    // 2. 遍歷目錄流, 列印目錄項
    errno = 0;
    struct dirent *entry;
    while ((entry = readdir(pdir)) != NULL) {
        char *name = entry->d_name;
        // 忽略 . 和 ..
        if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
            continue;
        }
        
        // 列印目錄項
        for (int i = 0; i < depths; i++) {
            printf(" ");
        }
        puts(name);

        // 遞迴遍歷目錄
        if (entry->d_type == DT_DIR) {
            directories++;
            // 拼接路徑名
            char subpath[256];
            sprintf(subpath, "%s/%s", path, name );
            dfs_print(subpath, depths + 4);
        }
        else {
            files++;
        }
    }   // curr == NULL
    if (errno) {
        error(1, errno, "readdir");
    }

    // 3. 關閉目錄流
    closedir(pdir);
}

遞迴複製目錄

在實現遞迴複製目錄時,不僅涉及到對目錄的複製,還涉及到對檔案的複製

#include <func.h>

void copyFile(const char* src, const char* dir);
void copyDir(const char* src, const char* dir);

int main(int argc, char* argv[])
{
    // ./copyDir src dst
    if (argc != 3){
        error(1, 0, "Usage: %s src dst", argv[0]);
    }
    copyDir(argv[1], argv[2]);
    return 0;
}

void copyFile(const char* src, const char* dst){
    // 複製檔案
    // 1. 開啟檔案流
    int src_fd = open(src, O_RDONLY);
    if (src_fd == -1) {
        error(1, errno, "open %s", src);
    }

    int dst_fd = open(dst, O_WRONLY | O_CREAT | O_EXCL);
    if (dst_fd == -1) {
        error(1, errno, "open %s", dst);
    }
    
    char buf[1024];
    int bytes;
    while ((bytes=read(src_fd, buf, sizeof(buf))) > 0){
        write(dst_fd, buf, bytes);
    }

    // 3. 關閉檔案流
    close(src_fd);
    close(dst_fd);
    
    if (bytes < 0) {
        error(1, errno, "read");
    }
}

void copyDir(const char* src, const char* dst){
    // 建立dst目錄
    int err = mkdir(dst, 0777);
    if (err) {
        error(1, errno, "mkdir %s", dst);
    }

    // 開啟src目錄
    DIR* psrc = opendir(src);
    if (!psrc) {
        error(1, errno, "opendir %s", src);
    }

    // 遍歷目錄流
    errno = 0;
    struct dirent* pcurr;
    while ((pcurr = readdir(psrc)) != NULL){
        //
        // 忽略.and..
        const char* name = pcurr->d_name;
        if (strcmp(name, ".")==0 || strcmp(name, "..") ==0) {
            continue;
        }
        
        char subsrc[256];
        char subdir[256];
        sprintf(subsrc, "%s/%s", src, name);
        sprintf(subdir, "%s/%s", dst, name);
        // 如果該目錄項是目錄,則呼叫copyDir遞迴複製目錄
        if (pcurr->d_type == DT_DIR) {
            copyDir(subsrc, subdir);
        }

        // 如果該目錄項是檔案,則呼叫copyFile複製檔案
        if (pcurr->d_type == DT_REG) {
            copyFile(subsrc, subdir);
        }
    }
    if (errno) {
       error(1, errno, "readdir"); 
    }

    // 3. 關閉檔案流
    closedir(psrc);
}

遞迴刪除目錄

#include <func.h>

void deleteDir(const char *path){
    // 1. 開啟目錄流
    DIR *pdir = opendir(path);
    if (!pdir) {
        error(1, errno, "opendir %s", path);
    }

    // 2. 遞迴刪除目錄流
    errno = 0;
    struct dirent *curr;
    while ((curr = readdir(pdir)) != NULL) {
        // 忽略. and ..
        char *filename = curr->d_name;
        if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) {
            continue;
        }
        
        char subpath[256];
        sprintf(subpath, "%s/%s", path, filename);
        if (curr->d_type == DT_DIR) {
            deleteDir(subpath);
        }
        else {
            unlink(subpath);
        }
    }   // curr == NULL
    if (errno) {
        error(1, errno, "readdir");
    }
    
    // 刪除當前目錄
    rmdir(path);

    // 3. 關閉檔案流
    closedir(pdir);
}

int main(int argc, char* argv[])
{
    // ./ deleteDir dir
    if (argc != 2) {
        error(1, 0, "Usage: %s dir\n", argv[0]);
    }
    // 刪除目錄
    deleteDir(argv[1]);
    return 0;
}

常用技巧

使用命令檢視型別

以檢視ino_t結構體為例

gcc -E .c檔案 | grep -nE "ino_t"

相關文章