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

Set*_*jmp 17 c directory posix

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

基本上,我们的目标是:

rm -rf <target>
Run Code Online (Sandbox Code Playgroud)

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

caf*_*caf 43

使用nftw()带有FTW_DEPTH标志的(File Tree Walk)功能.提供一个只调用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);
}
Run Code Online (Sandbox Code Playgroud)

  • @Jon:是的,但``nftw()`的`FWD_DEPTH`标志意味着在目录本身传递给`remove()`之前,目录的内容将被删除,因此在那时它将是空的. (4认同)
  • 哇,我学到了一些新东西.不知道`remove`可以删除目录. (3认同)
  • `remove()`只会删除*blank*目录. (2认同)

Jon*_*ler 15

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

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

  • +1推荐'nftw`,这已经让我大吃一惊.确保你使用'FTW_DEPTH`选项使用`nftw`,否则你将得到一个预订遍历,这对于`rmdir`-ing目录不起作用. (4认同)

PAD*_*MKO 6

您可以在纯C编程语言上编写自己的实现命令"rm -rf".源代码仅基于头文件:dirent.h,sys/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);
    }

    // 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;
}
Run Code Online (Sandbox Code Playgroud)

检查一下.

我使用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
Run Code Online (Sandbox Code Playgroud)

运行脚本

$ ./script.sh 
Run Code Online (Sandbox Code Playgroud)

生成文件/文件夹结构

$ 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
Run Code Online (Sandbox Code Playgroud)

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

$ cc -o -Wall -Werror -o rmtree rmtree.c
Run Code Online (Sandbox Code Playgroud)

删除目录dir1/dir1.1

$ ./rmtree dir1/dir1.1
Removed a file: dir1/dir1.1/file.scala
Removed a directory: dir1/dir1.1
Run Code Online (Sandbox Code Playgroud)

删除目录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
Run Code Online (Sandbox Code Playgroud)

删除目录dir1 /

$ ./rmtree dir1
Removed a directory: dir1/dir1.3
Removed a directory: dir1
Run Code Online (Sandbox Code Playgroud)

删除目录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
Run Code Online (Sandbox Code Playgroud)

删除目录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
Run Code Online (Sandbox Code Playgroud)

删除目录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
Run Code Online (Sandbox Code Playgroud)

删除目录dir3

$ ./rmtree dir3
Removed a file: dir3/file.js
Removed a file: dir3/file.java
Removed a directory: dir3
Run Code Online (Sandbox Code Playgroud)

删除目录dir4

$ ./rmtree dir4
Removed a file: dir4/file.py
Removed a directory: dir4
Run Code Online (Sandbox Code Playgroud)

删除一个空目录dir5

$ ./rmtree dir5
Removed a directory: dir5
Run Code Online (Sandbox Code Playgroud)

如果传递的路径不存在或不是目录的路径,您可以看到下一个:

$ ./rmtree rmtree.c
Is not directory: rmtree.c
$ ./rmtree 11111111111111111
Is not directory: 11111111111111111
Run Code Online (Sandbox Code Playgroud)

查看结果

$ tree
.
??? rmtree
??? rmtree.c
??? script.sh

0 directories, 3 files
Run Code Online (Sandbox Code Playgroud)

测试环境

$ 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
Run Code Online (Sandbox Code Playgroud)

  • 你好,我们又见面了!我忘了提及您的代码正在通过同一变量 full_path 泄漏内存 (2认同)

Set*_*jmp 5

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

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

rm依赖于以下功能:

fts_open
fts_read
fts_set
fts_close
Run Code Online (Sandbox Code Playgroud)

在linux和mac上都有手册页.

  • `nftw` 和 `ftw` 是更高级别的函数,因为它们基于 `fts_` 系列函数。 (2认同)