Как удалить каталог и его содержимое в (POSIX) C?

Меня больше всего интересует нерекурсивный случай, но я предполагаю, что другие, которые могут отслеживать этот вопрос, предпочли бы видеть рекурсивный случай.

В принципе, мы стремимся:

rm -rf <target>

Однако системный вызов был бы незрелым ответом.

Ответ 1

  • Вам нужно использовать nftw() (или, возможно, ftw()) для перемещения по иерархии.
  • Вам нужно использовать unlink() для удаления файлов и других не-каталогов.
  • Для удаления (пустых) каталогов вам необходимо использовать rmdir().

Вам было бы лучше использовать nftw() (а не ftw()), так как он предоставляет вам элементы управления, такие как FTW_DEPTH, чтобы все файлы под каталогом были посещены до того, как посетил сам каталог.

Ответ 2

Используйте функцию nftw() (File Tree Walk) с флагом FTW_DEPTH. Предоставьте обратный вызов, который просто вызывает 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);
}

Ответ 3

Я только что открыл источник GNU rm и посмотрю, что именно он делает:

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

rm полагается на следующие функции:

fts_open
fts_read
fts_set
fts_close

у которых есть справочные страницы на Linux и Mac.

Ответ 4

См. man 2 unlink и man 2 rmdir для системных вызовов, которые будут удалять файлы и (пустые) каталоги соответственно. Все, что вам нужно сделать, чтобы справиться с рекурсивным случаем, - это пересечь целевой каталог в последующем обходном порядке после первого порядка и удалить каждую запись в этом порядке с правильной процедурой удаления. Вы можете использовать opendir, readdir и closedir для перемещения по структуре каталогов.

Ответ 5

В псевдокоде здесь используется нерекурсивный подход:

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 как упражнение для читателя.: -)

(Edit: Кроме того, если это не чисто учебное упражнение, не изобретайте это колесо - было бы намного проще и, следовательно, менее подверженным ошибкам, использовать ftw или nftw, как предложили другие).

Ответ 6

Вы можете написать собственную команду реализации "rm -rf" на чистом языке программирования C. Исходный код основан только на заголовках: dirent.h, sys/stat.h и unistd.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;
}

Изучите его.

Я использую оболочку script для генерации структуры файлов/папок.

$ 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

$ ./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

Создайте исходный код файла rmtree.c с помощью GCC

$ 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

Ответ 7

man 2 rmdir

Пройдите путь по каталогу и удалите каждый файл и каталог.

Ответ 8

remove(char *path)
{
   struct stat statbuf
   stat(path, statbuf);
   if(path is directory)
   {
       opendir(path);
       while(end of directory is reached)
       {
         stat on directory entry;
         remove(directoryentry);
       }
    system("rmdir path");
   }
   else
   system("rm path");
}

Ответ 9

#include <unistd.h>
rmdir("/etc/mydir");

Каталог должен быть пустым.