如何在(POSIX)C中删除目录及其内容?
我对非递归情况最感兴趣,但我猜测其他可能跟踪这个问题的人会更喜欢看到递归情况。
基本上,我们的目标是:
rm -rf
但是,系统调用将是一个不成熟的答案。
- 您需要使用
nftw()
(或可能是ftw()
)来遍历层次结构。 - 您需要使用
unlink()
来删除文件和其他非目录。 - 您需要使用
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.h , sys / stat.h和unistd.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 unlink
和man 2 rmdir
。 为了处理递归情况,您需要做的就是在后序深度优先遍历中遍历目标目录,并使用正确的删除例程删除该顺序中的每个条目。 您可以使用opendir
, readdir
和closedir
遍历目录结构。
在伪代码中,这是我将采取的非递归方法:
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就像其他人建议的那样容易,因此不易出错。)