Search code examples
crecursiondirectorysubdirectory

Recursively listing all subdirectory of a given directory


I have to recursively list all the subdirectories of a given directory and I got it working, but it lists them out of order. I want it to list all the subdirectories of the given directory and then move on to the next subdirectory and I know it's because my recursion is inside the while loop, but I haven't been able to figure out how to implement it outside the loop. I thought about making an array of strings with the paths of the subdirectory and use it as a stack, but after looking it up, I don't think that is possible.

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

int listDir(char *name)
{
    DIR *dir;
    struct dirent *cDir;

    dir = opendir(name);
    if(dir != NULL)
    {
        while((cDir=readdir(dir)) != NULL)
        {
            char* subName = cDir->d_name;       
            if(strcmp(subName, ".")==0 || strcmp(subName, "..")==0)
                continue;
            else
            {
                //  Checks if it's a directory
                if(cDir->d_type == DT_DIR)        
                {
                    printf("%s\n", subName);
                    char *path;
                    path = malloc(sizeof(name) + sizeof(subName) + 2);
                    strcat(path, name);
                    strcat(path, "/");
                    strcat(path, subName);
                    listDir(path);                  
                }   
            }
        }


        closedir(dir);
    }

}

int main(int argc, char *argv[])
{   
    printf("%s\n", argv[1]);
    listDir(argv[1]);

    return 0;
}

Solution

  • To print the directory structure in a BFS, you could use a queue. Since C doesn't have such a thing in its standard library, you have to roll your own or use a library. Here's a simple example of how this might work, alongside a cleaned up version of your DFS.

    #include <dirent.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    void listDirBFS(char *name)
    {
        int q_front = 0;
        int q_back = 1;
        int q_cap = 4;
        char **q = malloc(q_cap * sizeof(*q));
    
        for (q[0] = strdup(name); q_front != q_back;) 
        {
            name = q[q_front++];
            DIR *dir = opendir(name);
    
            if (!dir) continue;
    
            printf("%s\n", name);
            size_t name_len = strlen(name);
            struct dirent *cDir;
    
            while ((cDir = readdir(dir)))
            {
                char *subName = cDir->d_name;
    
                if (strcmp(subName, ".") && strcmp(subName, "..") &&
                    cDir->d_type == DT_DIR)
                {
                    char *path = malloc(name_len + strlen(subName) + 2);
                    sprintf(path, "%s/%s", name, subName);
    
                    if (q_back >= q_cap && 
                        !(q = realloc(q, sizeof(*q) * (q_cap *= 2)))) 
                    {
                        fprintf(stderr, "%s:%d realloc\n", __FILE__, __LINE__);
                        exit(1);
                    }
    
                    q[q_back++] = path;
                }
            }
    
            free(name);         
            closedir(dir);
        }
    
        free(q);
    }
    
    void listDir(char *name)
    {
        DIR *dir = opendir(name);
    
        if (!dir) return;
    
        printf("%s\n", name);
        size_t name_len = strlen(name);
        struct dirent *cDir;
    
        while ((cDir = readdir(dir)))
        {
            char *subName = cDir->d_name;
    
            if (strcmp(subName, ".") && strcmp(subName, "..") &&
                cDir->d_type == DT_DIR)
            {
                char *path = malloc(name_len + strlen(subName) + 2);
                sprintf(path, "%s/%s", name, subName);
                listDir(path);
                free(path);
            }
        }
    
        closedir(dir);
    }
    
    int main(int argc, char **argv)
    {   
        puts("== DFS ==");
        listDir(".");
        puts("\n== BFS ==");
        listDirBFS(".");
        return 0;
    }
    

    Output:

    == DFS ==
    .
    ./a
    ./a/b
    ./a/b/e
    ./a/bb
    ./a/bb/f
    ./a/bb/f/g
    ./aa
    ./aa/c
    ./aaa
    ./aaa/d
    ./aaa/d/h
    ./aaa/d/hh
    
    == BFS ==
    .
    ./a
    ./aa
    ./aaa
    ./a/b
    ./a/bb
    ./aa/c
    ./aaa/d
    ./a/b/e
    ./a/bb/f
    ./aaa/d/h
    ./aaa/d/hh
    ./a/bb/f/g
    

    A few remarks and suggestions on your code:

    • sizeof(name) + sizeof(subName) is incorrect. sizeof returns the size of the pointers; strlen is probably what you intended to get the lengths of the pointed-to strings. This avoids undefined behavior due to possibly strcat-ing beyond the allocated memory.
    • Always free memory after use to avoid memory leaks.
    • Compiling with the -Wall flag to turn warnings on reveals that control reaches the end of a non-void function. Change your return type from int to void if you're not actually returning an integer.
    • Note that I changed where the prints occur. You can move them back to the location of the recursive call, but I prefer to do the work for each node at the top of the function before exploring children. The algorithms are fundamentally the same.