如何在(POSIX)C中删除目录及其内容?

我对非递归情况最感兴趣,但我猜测其他可能跟踪这个问题的人会更喜欢看到递归情况。

基本上,我们的目标是:

rm -rf  

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

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

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

使用带有FTW_DEPTH标志的nftw() (文件树步行)function。 提供一个只调用传递文件上的remove()的回调:

 #define _XOPEN_SOURCE 500 #include  #include  #include  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); } 

我刚刚破解了GNU rm源代码,看看它究竟是做什么的:

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

rm依赖于以下function:

 fts_open fts_read fts_set fts_close 

在linux和mac上都有手册页。

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


文件rmtree.c

 #include  #include  #include  // POSIX dependencies #include  #include  #include  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); } // 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 

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

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

 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就像其他人建议的那样容易,因此不易出错。)