1

I'm trying to create a program where I can search for some files in a directory on my PC, using Visual Studio C++. As I'm not very experienced with that, I found this code (below) in another answer but couldn't find any explanation to the code. I'm having a hard time figuring it out and would strongly appreciate any help possible.

If there's another way of doing this I would be pleased to know how. Thank you!

" Now you can get file names. Just compare a file name.

 while ((dirp = readdir(dp)) != NULL) {
       std::string fname = dirp->d_name;
       if(fname.find("abc") != std::string::npos)
          files.push_back(fname);
    }

Also you can use scandir function which can register filter function.

 static int filter(const struct dirent* dir_ent)
    {
        if (!strcmp(dir_ent->d_name, ".") || !strcmp(dir_ent->d_name, "..")) 
    return 0;
        std::string fname = dir_ent->d_name;

        if (fname.find("abc") == std::string::npos) return 0;

        return 1;
    }


    int main()
    {
        struct dirent **namelist;

        std::vector<std::string> v;
        std::vector<std::string>::iterator  it;

        n = scandir( dir_path , &namelist, *filter, alphasort );

        for (int i=0; i<n; i++) {
            std::string fname = namelist[i]->d_name;

            v.push_back(fname);

            free(namelist[i]);
        }
        free(namelist);

    return 0;
    }

"

Gus
  • 45
  • 1
  • 8
  • 1
    It's not clear what you can't figure out. Can you be more specific about which parts you don't follow? Or what's going wrong (if anything)? – doctorlove Jul 17 '18 at 10:03
  • Sure. I copied the lower piece of code ( from -> static int filter(const to -> return 0;} ) and pasted it as is in my code. I then tried to apply it to my case. The problem is that I don't know which pieces I should change (for example "abc" change it to the name of the file I am searching for) to apply it to my case. Also, what output should I expect? – Gus Jul 17 '18 at 10:08
  • In `main` you don't appear to be using the `it` and perhaps you should write out the contents of `v` to see what it gets? – doctorlove Jul 17 '18 at 10:15
  • Are you unsure what the `find` method on string does? Is the question really what to put in the filter function? – doctorlove Jul 17 '18 at 10:20
  • Where should I use "it" and what should I write as a content for "v"? – Gus Jul 17 '18 at 10:31
  • I really have no idea what the find and filter function do. – Gus Jul 17 '18 at 10:31

2 Answers2

2

A better way of doing this would probably be using the new std::filesystem library. directory_iterators allow you to go through the contents of a directory. Since they are just iterators, you can combine them with standard algorithms like std::find_if to search for a particular entry:

#include <filesystem>
#include <algorithm>

namespace fs = std::filesystem;

void search(const fs::path& directory, const fs::path& file_name)
{
    auto d = fs::directory_iterator(directory);

    auto found = std::find_if(d, end(d), [&file_name](const auto& dir_entry)
    {
        return dir_entry.path().filename() == file_name;
    });

    if (found != end(d))
    {
        // we have found what we were looking for
    }

    // ...
}

We first create a directory_iterator d for the directory in which we want to search. We then use std::find_if() to go through the contents of the directory and search for an entry that matches the filename we are looking for. std::find_if() expects a function object as last argument that is applied to every visited element and returns true if the element matches what we are looking for. std::find_if() returns the iterator to the first element for which this predicate function returns true, otherwise it returns the end iterator. Here, we use a lambda as predicate that returns true when the filename component of the path of the directory entry we're looking at matches the wanted filename. Afterwards, we compare the iterator returned by std::find_if() to the end iterator to see if we have found an entry or not. In case we did find an entry, *found will evaluate to a directory_entry representing the respective file system object.

Note that this will require a recent version of Visual Studio 2017. Don't forget to set the language standard to /std:c++17 or /std:c++latest in the project properties (C++/Language).

Michael Kenzel
  • 15,508
  • 2
  • 30
  • 39
  • Great, can you provide me with a little more explanation on what the above code does? – Gus Jul 17 '18 at 11:25
  • I've edited my answer, simplified the code a little, and added more explanation. – Michael Kenzel Jul 17 '18 at 12:02
  • Thanks a lot. The program worked. However, this worked only when I gave the exact directory (with no sub directory). In my case, I have a big folder e.g folder A. This folder contains different sub-folders called B C D. In each sub-folder are another bunch of sub-folders before finally accessing the different files of different formats. Can you advise on how to modify the code so that it goes through all the sub-folders? – Gus Jul 17 '18 at 13:57
  • Also, I'm not sure if I passed the right parameters to the function search. Is the following correct? search("C:\\FolderA\\FolderB\\FolderF" , "file-name"); – Gus Jul 17 '18 at 14:01
  • Simply use a `recursive_directory_iterator` instead of a normal `directory_iterator`. The recursive one will visit the directory and all subdirectories. And yes, this way of calling it should work. – Michael Kenzel Jul 17 '18 at 14:55
  • Great it actually worked. Now one thing that was nice in the first code I posted in my initial question, is that I could search for example "1234" without searching for the exact name which is "1234_0506AB". Any idea how I can integrate that into the recursive code? Your help is much appreciated. – Gus Jul 18 '18 at 06:46
  • Well, instead of simply testing for equality via `dir_entry.path().filename() == file_name`, just get the filename as string `dir_entry.path().filename().string()` and do a `.find()`. For even more general searching, you could use a regex and match that with the filename… – Michael Kenzel Jul 18 '18 at 14:01
0

Both methods use the find function of a std::string:

   fname.find("abc")

This looks for "abc" in the fname string. If it's found it returns the index it starts at, otherwise it retruns std::string::npos, so they both check for that substring.

You may want to see if you have an exact match, using == instead. It depends.

If an appropriate filename is found, it's pushed back into a vector. Your main function has

std::vector<std::string>::iterator  it;

which it doesn't use. I suspect that came over with some copy/paste.

You can use a range based for loop to see what's in your vector:

for(const std::string & name : v)
{
    std::cout << name << '\n';
}

The filter function also checks against "." and ".." since these have special meanings - current dir and up one dir. At that point, th C API has returned a char *, so they use strcmp, rather than std::string methods.


Edit:

n = scandir( dir_path , &namelist, *filter, alphasort );

uses n which you haven't declared. Try

int n = scandir( dir_path , &namelist, *filter, alphasort );

Also, that uses dir_path which needs declaring somewhere.

For a quick fix, try

const char * dir_path = "C:\\";

(or whatever path you want, watching out for escaping backslashes with an extra backslash.

You probably want to pass this in as an arg to main.

doctorlove
  • 18,872
  • 2
  • 46
  • 62
  • Thanks a lot. The program worked. However, this worked only when I gave the exact directory (with no sub directory). In my case, I have a big folder e.g folder A. This folder contains different sub-folders called B C D. In each sub-folder are another bunch of sub-folders before finally accessing the different files of different formats. Can you advise on how to modify the code so that it goes through all the sub-folders? – Gus Jul 17 '18 at 13:58
  • If you find a directory (look in `namelist[i]`) you can recusive i.e. invoke your search down that dir e.g. https://stackoverflow.com/questions/8436841/how-to-recursively-list-directories-in-c-on-linux but you will then need to pull the code out of `main` into a function – doctorlove Jul 17 '18 at 14:48