How to find a file in a directory and its subdirectories in C on Linux?

Viewed 108

I want to write a program that given a filename searches in the current directory and its subdirectories for that file. The names of the subdirectories containing that file should be printed.

But my code prints just the subdirectories.

#include <dirent.h>
#include <stdio.h>
#include <string.h>

void listDir(char *path, char *name)
{
    DIR *dir;
    struct dirent *ent;
    if ((dir = opendir(path)) != NULL) {
        while (( ent = readdir(dir)) != NULL) {
            if (ent->d_type == DT_REG && name == ent->d_name) {
                printf("%s\n", ent->d_name);
            }

            if (ent->d_type == DT_DIR && strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0) {
                printf("%s\n", ent->d_name);
                listDir("/home/dir1/ent->d_name", name);
            }
        }
        closedir(dir);
    }
}

void main()
{
    listDir("/home/dir1", "file1");
}
2 Answers

You either need to keep track of the full path, or descend into each directory. (if you are looking at foo/bar/baz, then opendir("baz") will fail if you are in the top level directory). Something like:

#include <dirent.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>

void
listDir(char* path, const char *name)
{
        DIR* dir;
        struct dirent *ent;

        char *end = strrchr(path, '\0');

        if( (dir = opendir(path)) == NULL ){
                perror(path);
                return;
        } else while( (ent = readdir(dir)) != NULL ){
                if( ent->d_type == DT_DIR && strcmp(ent->d_name, ".")
                                 && strcmp(ent->d_name, "..") )
                {
                        snprintf(end, PATH_MAX - (end - path), "/%s",
                                ent->d_name);
                        listDir(path, name);
                        *end = '\0';
                } else if( !strcmp(ent->d_name, name) ){
                        printf("%s\n", path);
                }
        }
        closedir(dir);
}

int
main(int argc, char **argv)
{
        char path[PATH_MAX];
        char *defaults[] = {NULL, NULL, ".", NULL};
        if( argc < 2 ){
                printf("usage: %s name [path...]", argv[0]);
                return EXIT_SUCCESS;
        }
        const char *name = argv[1];
        if( argc < 3 ){
                argv = defaults;
        }
        for( argv += 2; *argv; argv += 1 ){
                /* getcwd(fullpath, PATH_MAX); */
                snprintf(path, PATH_MAX, "%s", *argv);
                listDir(path, name);
        }
        return 0;
}

There are multiple problems in your code:

  • the comparison name == ent->d_name does not work for string pointers, you should use !strcmp(name, ent->d_name).

  • listDir("/home/dir1/ent->d_name", name); does not magically concatenate strings. You should use snprintf or asprintf (on linux and BSD systems) to construct the subdirectory name.

Here is a modified version:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void listDir(const char *path, const char *name) {
    DIR *dir;
    struct dirent *ent;

    if ((dir = opendir(path)) != NULL) {
        while ((ent = readdir(dir)) != NULL) {
            if (ent->d_type == DT_REG && !strcmp(name, ent->d_name)) {
                printf("%s\n", path);
            }
            if (ent->d_type == DT_DIR && strcmp(ent->d_name, ".")
                                      && strcmp(ent->d_name, "..")) {
                char *subdir;
                size_t len = strlen(path);
                const char *sep = "/";
                if (len && path[len - 1] == '/')
                    sep++;
                if (asprintf(&subdir, "%s%s%s", path, sep, ent->d_name) > 0) {
                    listDir(subdir, name);
                    free(subdir);
                }
            }
        }
        closedir(dir);
    }
}

int main(int argc, char *argv[]) {
    if (argc == 3) {
        listDir(argv[1], argv[2]);
    } else {
        fprintf(stderr, "usage: %s FILE DIR\n", argv[0]);
    }
    return 0;
}
Related