0

I'm looking for a cross platform function that supports wildcard listing of a directory contents similar to what FindFirstFile on windows.

Is the wildcard pattern accepted in windows very specific to windows? I want something that supports FindFirstFile wildcard pattern but he working in Linux as well.

Captain Jack sparrow
  • 959
  • 1
  • 13
  • 28
  • `man 3 glob` is for you – 273K May 22 '20 at 16:14
  • https://en.cppreference.com/w/cpp/filesystem - That's your, standard provided, cross-platform interface. – Jesper Juhl May 22 '20 at 16:38
  • But it doesn't do wildcard matching @JesperJuhl – Captain Jack sparrow May 22 '20 at 16:58
  • 1
    Isn't glob Linux only @S.M. – Captain Jack sparrow May 22 '20 at 16:59
  • @CaptainJacksparrow No, it doesn't. But, that's the interface you have to work with if you want a standard provided, cross platform thing. If that's not good enough for your needs, you are going to have to write platform specific code for the various platforms you care about - possibly abstracted away behind an interface you create that irons out / handles the platform details to provide a uniform behaviour to the user. – Jesper Juhl May 22 '20 at 17:05
  • Strange comment. You have asked about smth similar to `FindFirstFile` on Linux and `glob` is it. – 273K May 22 '20 at 18:11
  • 1
    I asked for something cross platform that would not involve putting ifdefs for OS – Captain Jack sparrow May 22 '20 at 19:52
  • see [this portable C++ version with `std::filesystem`](https://stackoverflow.com/a/53728039/995714). See also [How can I get the list of files in a directory using C or C++?](https://stackoverflow.com/a/24703135/995714) – phuclv Feb 10 '21 at 01:16

2 Answers2

2

If C++17 and above: You can "walk" a directory using a directory iterator, and match walked file names with a regex, like this:

static std::optional<std::string> find_file(const std::string& search_path, const std::regex& regex) {
    const std::filesystem::directory_iterator end;
    try {
        for (std::filesystem::directory_iterator iter{search_path}; iter != end; iter++) {
            const std::string file_ext = iter->path().extension().string();
            if (std::filesystem::is_regular_file(*iter)) {
                if (std::regex_match(file_ext, regex)) {
                    return (iter->path().string());
                }
            }
        }
    }
    catch (std::exception&) {}
    return std::nullopt;
}

Usage would be for example, for finding the first file, that ends in .txt:

auto first_file = find_file("DocumentsDirectory", std::regex("\\.(?:txt)"));

Similarly, if you are interested in more than matching by extension, the function line

const std::string file_ext = iter->path().extension().string();

should be modified to something that captures the part of the filename you are interested in (or the whole path to the file)

This could then be used in a function, which performs the wildcard listing by directory.

bochko
  • 363
  • 4
  • 14
  • problem is that accepted wildcards are different from regex. For instance you can do `ls a*` – Captain Jack sparrow May 22 '20 at 18:04
  • The specific format of wildcards is NOT connected to the underlying filesystem. "ls" is a system tool, and there should be no expectation that c++, the language, (or any OS for that matter) would support a wildcard translation. However I see nothing stopping you from using regex matching with std::filesystem to translate that behaviour to whatever accepted wildcards you want. – bochko May 22 '20 at 18:16
0

Here is a recursive variant.

It calls a functional f for each file in the list and returns the number of files found. It is also recursive: it descends sub directories to the max depth specified. Note that the search filter does a filename is matched.

The try-catch block in removed so that the caller can catch and process any problems.

#include <string>
#include <regex>
#include <filesystem>

// recursively call a functional *f* for each file that matches the expression
inline int foreach_file(const std::string& search_path, const std::regex& regex, int depth, std::function<void(std::string)> f) {
    int n = 0;
    const std::filesystem::directory_iterator end;
    for (std::filesystem::directory_iterator iter{ search_path }; iter != end; iter++) {
        const std::string filename = iter->path().filename().string();
        if (std::filesystem::is_regular_file(*iter)) {
            if (std::regex_match(filename, regex)) {
                n++;
                f(iter->path().string());
            }
        }
        else if (std::filesystem::is_directory(*iter) && depth>0) {
            n += foreach_file(iter->path().string(), regex, depth - 1, f);
        }
    }
    return n;
}

Example:


void do_something(string filename) {
...
}


void do_all_json_that_start_with_z() {
    // regex matches the whole filename 
    regex r("z.*.json", regex::ECMAScript | regex::icase); // ignoring case
    foreach_file(R"(C:\MyFiles\)", r, 99, do_something); // max depth 99
}

// can use lambdas
void do_all_json_that_start_with_z() {
    int n=0;
    foreach_file(
        R"(C:\MyFiles\)", // using raw string - for windows
        regex("z.*.json"),
        0,                // do not descend to sub-directories 
        [&n](string s) { printf("%d) %s\n", ++n, s.c_str()); });
}



QT-1
  • 900
  • 14
  • 21