【问题标题】:How to delete a directory and its contents in (POSIX) C? [duplicate]如何删除(POSIX)C中的目录及其内容? [复制]
【发布时间】:2011-07-24 23:23:25
【问题描述】:

我对非递归案例最感兴趣,但我猜其他可能跟踪此问题的人更愿意看到递归案例。

基本上,我们的目标是:

rm -rf <target>

但是,系统调用将是一个不成熟的答案。

【问题讨论】:

    标签: c directory posix


    【解决方案1】:

    请参阅man 2 unlinkman 2 rmdir,了解将分别删除文件和(空)目录的系统调用。为了处理递归情况,您需要做的就是以后序深度优先遍历的方式遍历目标目录,并使用正确的删除例程按该顺序删除每个条目。可以使用opendirreaddirclosedir遍历目录结构。

    【讨论】:

      【解决方案2】:
      1. 您需要使用nftw()(或者可能是ftw())来遍历层次结构。
      2. 您需要使用unlink() 来删除文件和其他非目录。
      3. 您需要使用rmdir() 删除(空)目录。

      您最好使用nftw()(而不是ftw()),因为它提供了诸如FTW_DEPTH 之类的控件,以确保在访问目录本身之前访问目录下的所有文件。

      【讨论】:

      • +1 推荐了nftw,这让我不以为然。确保将nftwFTW_DEPTH 选项一起使用,否则您将获得预购遍历,这不适用于rmdir-ing 目录。
      • @Dave:信不信由你——在看到您的评论之前,我添加了关于使用 nftw()FTW_DEPTH 的评论。我同意你的看法。
      • 每个具有ntfw() 的系统也具有remove(),当在目录上调用时其行为类似于rmdir(),在路径上调用时类似于unlink()。所以总是使用remov() 并使用ntfwFTW_DEPTH 就可以了。
      • 哦,有人应该注意到ntfw() 绝对不是线程安全的,如果其他线程更改当前工作目录,它甚至可能会中断 - 一个好的实现可能不会关心但 POSIX 标准说ntfw() 可以依赖设置的工作目录在处理过程中保持设置,并且一些简单的实现可能会这样做。
      【解决方案3】:

      使用带有FTW_DEPTH 标志的nftw()(文件树遍历)功能。提供一个只在传递的文件上调用remove() 的回调:

      #define _XOPEN_SOURCE 500
      #include <stdio.h>
      #include <ftw.h>
      #include <unistd.h>
      
      int unlink_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
      {
          int rv = remove(fpath);
      
          if (rv)
              perror(fpath);
      
          return rv;
      }
      
      int rmrf(char *path)
      {
          return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
      }
      

      【讨论】:

      • 哇,我学到了一些新东西。不知道remove 可以删除目录。
      • remove() 只会删除 blank 目录。
      • @Jon: 是的,但是nftw()FWD_DEPTH 标志意味着在将目录本身传递给remove() 之前将删除目录的内容,因此它将为空那一点。
      • 我尝试使用这个优雅的实现,但在rm -rf成功的一种情况下失败了:如果有很多嵌套目录,remove会遇到ENAMETOOLONG错误,但rm -rf会使用openatunlinkat 一次使用一个目录名称,甚至删除嵌套很深的目录结构。
      • @OrivejDesh:听起来我们需要一个 nnftw()(新的、新的文件树遍历)来传递目录文件描述符和相对名称:)
      【解决方案4】:

      在伪代码中,这是我将采用的非递归方法:

      create a stack to hold directory names.
      push argv contents onto the stack
      while (stack !empty) {
          look at the top directory name on the stack
          for each item in directory {
              if (item is a directoy) {
                  push it onto the stack
              } else {
                  delete it
              }
          }
          if (no subdirs were pushed) {
              pop the top dir name from the stack
              delete it
          }
      }
      

      我将把在 C 中实现这个作为练习留给读者。 :-)

      (编辑:另外,除非这纯粹是一个学习练习,否则不要重新发明这个轮子 - 正如其他人建议的那样,使用 ftw 或 nftw 会容易得多,因此更不容易出错。)

      【讨论】:

        【解决方案5】:

        我刚刚破解了 GNU rm 源代码,看看它到底做了什么:

        http://www.gnu.org/software/coreutils/

        rm 依赖于以下函数:

        fts_open
        fts_read
        fts_set
        fts_close
        

        在 linux 和 mac 上都有手册页。

        【讨论】:

        • nftwftw 是更高级别的函数,因为它们基于 fts_ 系列函数。
        【解决方案6】:

        您可以在纯C编程语言上编写自己的实现命令“rm -rf”。仅基于标头的源代码:dirent.hsys/stat.hunistd.h。如果您需要将代码移植到其他系统,例如Windows,您只需要更改具有相应功能的标题,同时算法不会改变。


        一个文件rmtree.c

        #include <stdio.h>
        #include <string.h>
        #include <stdlib.h>
        
        // POSIX dependencies
        #include <dirent.h>
        #include <sys/stat.h>
        #include <unistd.h>
        
        
        void
        rmtree(const char path[])
        {
            size_t path_len;
            char *full_path;
            DIR *dir;
            struct stat stat_path, stat_entry;
            struct dirent *entry;
        
            // stat for the path
            stat(path, &stat_path);
        
            // if path does not exists or is not dir - exit with status -1
            if (S_ISDIR(stat_path.st_mode) == 0) {
                fprintf(stderr, "%s: %s\n", "Is not directory", path);
                exit(-1);
            }
        
            // if not possible to read the directory for this user
            if ((dir = opendir(path)) == NULL) {
                fprintf(stderr, "%s: %s\n", "Can`t open directory", path);
                exit(-1);
            }
        
            // the length of the path
            path_len = strlen(path);
        
            // iteration through entries in the directory
            while ((entry = readdir(dir)) != NULL) {
        
                // skip entries "." and ".."
                if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
                    continue;
        
                // determinate a full path of an entry
                full_path = calloc(path_len + strlen(entry->d_name) + 1, sizeof(char));
                strcpy(full_path, path);
                strcat(full_path, "/");
                strcat(full_path, entry->d_name);
        
                // stat for the entry
                stat(full_path, &stat_entry);
        
                // recursively remove a nested directory
                if (S_ISDIR(stat_entry.st_mode) != 0) {
                    rmtree(full_path);
                    continue;
                }
        
                // remove a file object
                if (unlink(full_path) == 0)
                    printf("Removed a file: %s\n", full_path);
                else
                    printf("Can`t remove a file: %s\n", full_path);
                free(full_path);
            }
        
            // remove the devastated directory and close the object of it
            if (rmdir(path) == 0)
                printf("Removed a directory: %s\n", path);
            else
                printf("Can`t remove a directory: %s\n", path);
        
            closedir(dir);
        }
        
        
        int
        main(const int argc, char const *argv[])
        {
            if (argc != 2) {
                fprintf(stderr, "Missing single operand: path\n");
                return -1;
            }
        
            rmtree(argv[1]);
        
            return 0;
        }
        

        检查一下。

        我正在使用 shell 脚本来生成文件/文件夹结构。

        $ cat script.sh 
        
        mkdir -p dir1/{dir1.1,dir1.2,dir1.3}
        mkdir -p dir1/dir1.2/{dir1.2.1,dir1.2.2,dir1.2.3}
        mkdir -p dir2/{dir2.1,dir2.2}
        mkdir -p dir2/dir2.2/dir2.2.1
        mkdir -p dir2/dir2.2/{dir2.2.1,dir2.2.2}
        mkdir -p dir3/dir3.1
        mkdir -p dir4
        mkdir -p dir5
        
        touch dir1/dir1.1/file.scala
        touch dir1/dir1.2/file.scala
        touch dir2/dir2.2/{file.c,file.cpp}
        touch dir2/dir2.2/dir2.2.2/{file.go,file.rb}
        touch dir3/{file.js,file.java}
        touch dir3/dir3.1/{file.c,file.cpp}
        > dir4/file.py
        

        运行脚本

        $ ./script.sh 
        

        生成文件/文件夹结构

        $ tree
        .
        ├── dir1
        │   ├── dir1.1
        │   │   └── file.scala
        │   ├── dir1.2
        │   │   ├── dir1.2.1
        │   │   ├── dir1.2.2
        │   │   ├── dir1.2.3
        │   │   └── file.scala
        │   └── dir1.3
        ├── dir2
        │   ├── dir2.1
        │   └── dir2.2
        │       ├── dir2.2.1
        │       ├── dir2.2.2
        │       │   ├── file.go
        │       │   └── file.rb
        │       ├── file.c
        │       └── file.cpp
        ├── dir3
        │   ├── dir3.1
        │   │   ├── file.c
        │   │   └── file.cpp
        │   ├── file.java
        │   └── file.js
        ├── dir4
        │   └── file.py
        ├── dir5
        ├── rmtree.c
        └── script.sh
        
        16 directories, 13 files
        

        通过 GCC 构建文件 rmtree.c 的源代码

        $ cc -o -Wall -Werror -o rmtree rmtree.c
        

        删除目录 dir1/dir1.1

        $ ./rmtree dir1/dir1.1
        Removed a file: dir1/dir1.1/file.scala
        Removed a directory: dir1/dir1.1
        

        删除目录 dir1/dir1.2

        $ ./rmtree dir1/dir1.2
        Removed a directory: dir1/dir1.2/dir1.2.3
        Removed a file: dir1/dir1.2/file.scala
        Removed a directory: dir1/dir1.2/dir1.2.1
        Removed a directory: dir1/dir1.2/dir1.2.2
        Removed a directory: dir1/dir1.2
        

        删除目录 dir1/

        $ ./rmtree dir1
        Removed a directory: dir1/dir1.3
        Removed a directory: dir1
        

        删除目录 dir2/dir2.2/dir2.2.2

        $ ./rmtree dir2/dir2.2/dir2.2.2
        Removed a file: dir2/dir2.2/dir2.2.2/file.rb
        Removed a file: dir2/dir2.2/dir2.2.2/file.go
        Removed a directory: dir2/dir2.2/dir2.2.2
        

        删除目录 dir2/

        $ ./rmtree dir2
        Removed a directory: dir2/dir2.1
        Removed a file: dir2/dir2.2/file.c
        Removed a directory: dir2/dir2.2/dir2.2.1
        Removed a file: dir2/dir2.2/file.cpp
        Removed a directory: dir2/dir2.2
        Removed a directory: dir2
        

        删除目录 dir3/dir3.1

        $ ./rmtree dir3/dir3.1
        Removed a file: dir3/dir3.1/file.c
        Removed a file: dir3/dir3.1/file.cpp
        Removed a directory: dir3/dir3.1
        

        删除目录 dir3

        $ ./rmtree dir3
        Removed a file: dir3/file.js
        Removed a file: dir3/file.java
        Removed a directory: dir3
        

        删除目录 dir4

        $ ./rmtree dir4
        Removed a file: dir4/file.py
        Removed a directory: dir4
        

        删除一个空目录dir5

        $ ./rmtree dir5
        Removed a directory: dir5
        

        如果传递的路径不存在或者不是目录的路径,你会看到下一个:

        $ ./rmtree rmtree.c
        Is not directory: rmtree.c
        $ ./rmtree 11111111111111111
        Is not directory: 11111111111111111
        

        查看结果

        $ tree
        .
        ├── rmtree
        ├── rmtree.c
        └── script.sh
        
        0 directories, 3 files
        

        测试环境

        $ lsb_release -a
        No LSB modules are available.
        Distributor ID: Debian
        Description:    Debian GNU/Linux 8.7 (jessie)
        Release:    8.7
        Codename:   jessie
        $ uname -a
        Linux localhost 3.16.0-4-amd64 #1 SMP Debian 3.16.36-1+deb8u2 (2016-10-19) x86_64 GNU/Linux
        $ cc --version
        cc (Debian 4.9.2-10) 4.9.2
        

        【讨论】:

        • 嗨!您错过了 full_path calloc 中“/”的 +1
        • 您好!我忘了提到你的代码通过同一个变量 full_path 泄漏内存
        • 不错的程序,但由于首次亮相已经注意到内存问题。
        • full_path = calloc(path_len + strlen(entry-&gt;d_name) + 1, sizeof(char));释放它怎么样?
        • jj,不错的代码,谢谢。尽管对这两个错误很谨慎: path_len + strlen(entry->d_name) + 1 /* "/" / + 1 / eof */ 并在递归调用 rmtree 后释放内存
        猜你喜欢
        • 2014-01-07
        • 1970-01-01
        • 1970-01-01
        • 2011-03-21
        • 2016-01-01
        • 2011-12-07
        • 2023-04-02
        • 2010-10-23
        • 1970-01-01
        相关资源
        最近更新 更多