c++17 filesystem::remove_all with wildcard path

Viewed 1903

I want to delete all files, folders and subfolders but not the parent folder.

So I tried to use filesystem::remove_all with a wildcard, but this doesn't seems to work.

filesystem::removeall("pathtofolder/*");

There is no exception, but it doesn't delete anything.

Are wildcards are not allowed?

Do I really need to call for each file and folder inside of pathtofolder the removeall method?

4 Answers

Are wildcards are not allowed?

There is no support for globbing (wildcards) in std::filesystem::remove_all:

Deletes the contents of p (if it is a directory) and the contents of all its subdirectories, recursively, then deletes p itself as if by repeatedly applying the POSIX remove. Symlinks are not followed (symlink is removed, not its target)

Do I really need to call for each file and folder inside of pathtofolder the removeall method?

Yes, your call should therefore be something like this:

#include <cstdint>
#include <exception>
#include <filesystem>
#include <iostream>
#include <system_error>

int main() {
    try {
        std::uintmax_t count = 0;

        // loop over all the "directory_entry"'s in "pathtofolder":
        for(auto& de : std::filesystem::directory_iterator("pathtofolder")) {
            // returns the number of deleted entities since c++17:
            count += std::filesystem::remove_all(de.path());
        }
        std::cout << "deleted " << count << " files and directories\n";

    } catch(const std::exception& ex) {
        // The overloads that does not take a std::error_code& parameter throws
        // filesystem_error on underlying OS API errors, constructed with p as
        // the first path argument and the OS error code as the error code
        // argument.

        // directory_iterator:  throws if the directory doesn't exist
        // remove_all:          does NOT throw if the path doesn't exist

        std::cerr << ex.what() << std::endl;
    }
}

Note that if de.path() doesn't exist, it's not an OS API error and it will not throw an exception. It will return 0 deleted files and directories (or false prior to C++17).

However, directory_iterator() will throw a filesystem_error if pathtofolder doesn't exist.

@TedLyngmo offers a valid solution; I typically add these things into a utility header, e.g. in my src/util/filesystem.hpp I put:

namespace util {
namespace filesystem {

std::uintmax_t remove_all_inside(const std::filesystem::path& dir) {
    std::uintmax_t removed_items_count { 0 };

    if (not is_directory(dir)) { 
        throw std::invalid_argument("Not a directory: " + dir.str());
    }
    for(auto& dir_element : std::filesystem::directory_iterator(dir)) {
        removed_items_count += std::filesystem::remove_all(dir_element .path());
    }
    return removed_items_count;
}

} // namespace filesystem
} // namespace util

and then I can write:

#include <util/filesystem.hpp>

// ... etc etc ...

util::filesystem::remove_all_inside("/path/to/folder");

A nice aspect of this approach is that it can work with earlier versions of the standard - you just use some preprocessor magic to choose either std::filesystem or boost::filesystem based on the C++ version; but the code using the utility function stays the same.

Example:

std::wregex regExpName { LR"(^.*\.tmp$)" };
const auto tmpPath { std::filesystem::temp_directory_path() };
for (const auto& element : std::filesystem::directory_iterator(tmpPath))
    if (not element.is_directory() and std::regex_match(element.path().filename().wstring(), regExpName))
        std::filesystem::remove(element.path());
Related