I'm writing this program to enumerate every file in the directory to practice using pointers because I am new to the concept and the C language. The return statement to **getFiles() comes back empty for some reason. Weirdly, in the getFiles() function, when I print my 'array of strings' (dirs), it prints 1 of the 3 files I have in my directory. However, when I print the results in main(), nothing happens.
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
int main(){
char **getFiles();
printf("\nFUNCTION RETURN:\n%s", *getFiles());
return 0;
}
#define BUFFER_SIZE 4096
char **getFiles(){
char **dirs;
int total = 10; //Need to do this
dirs = malloc(total*sizeof(char *));
char buffer[BUFFER_SIZE];
struct dirent *de;
DIR *dr = opendir(".");
if (dr == NULL)
{
printf("Error");
return 0;
}
int x=0;
while ((de = readdir(dr)) != NULL){
int length = strlen(de->d_name);
dirs[x] = malloc(length*sizeof(char));
strcpy(buffer, de->d_name);
strcpy(dirs[x], buffer);
dirs[x] = buffer;
x++;
}
closedir(dr);
printf("\nPRINT DIRS\n:\n%s", *dirs);
return dirs;
for(int i =0; i<x;i++){
free(dirs[i]);
}
free(dirs);
}
As I said I'm still new to this whole pointers and addresses thing so I tried de-referencing a bunch of stuff and printing that but nothing really helped.
Another answer, hopefully more complete.
If you are going to read a directory’s list of names, do it all at once. The directory content can change at any time, even when you are reading its contents! So the more quickly you can get through it, the more likely the listing you get is correct.
You can monitor a directory for changes to be sure you got it right, but that is a subject I will only mention in passing here. Here’s a quickly-Googled link to start learning more: What is the proper way to use inotify?
To collect just a list of names relative to a given directory (using your code template):
// getFileNames()
// freeFileNames()
// countFileNames()
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
char **getFileNames(const char *dirname){
// Our final result will be a dynamically-allocated list
// of dynamically-allocated filenames (relative to dirname).
// The list ends with a NULL filename.
char **filenames = NULL;
// We wish to make a single pass through the directory, so we
// will use a linked list to collect filenames AND count them.
struct node {char *filename; struct node *next;};
struct node * head = NULL;
size_t count = 0;
// Scan through the directory,
// pushing each new filename to the head of the list
DIR *dir = opendir(dirname);
if (!dir) return NULL;
struct dirent *de;
while ((de = readdir(dir))){
struct node *node = malloc(sizeof(struct node));
if (!node) goto lerror;
node->filename = strdup(de->d_name);
node->next = head;
head = node;
count += 1;
}
closedir(dir);
// Now allocate the resulting array
// and move all the collected filenames to it,
// dismantling the linked list as we go
filenames = malloc((count+1) * sizeof(char*));
if (!filenames) goto lerror;
filenames[count] = NULL;
while (head){
struct node *next = head->next;
filenames[--count] = head->filename;
free(head);
head = next;
}
return filenames;
lerror:
// If something went wrong, we'll need to clean up
while (head){
struct node *next = head->next;
free(head->filename);
free(head);
head = next;
}
return NULL;
}
char **freeFileNames(char **filenames){
// Free each filename
for (size_t n = 0; filenames[n]; n++)
free(filenames[n]);
// And free the list
free(filenames);
return NULL;
}
size_t countFileNames(char **filenames){
size_t count = 0;
while (filenames[count])
count += 1;
return count;
}
Notice that I changed the function to take as argument the path (relative or absolute) to the directory you wish to examine. This is a good practice! Let the caller determine which directory we want to read without having to dink with first changing the CWD and then restoring it afterwards.
Because the function does not return the number of files in the list, the list itself is terminated with a NULL
string. This is a common design in C when dealing with lists of this type.
⟶ For example, main()
’s argc
is not really anything more than a convenience, because argv
ends with a NULL
string! That is to say, argv[argc] == NULL
!
And once again, it is important to note that the returned list of filenames is relative to the argument directory. You could return the full path of each file (== absolute path name). This would require help concatenating the directory name and filename before strdup()
ing it into the resulting list.
Or you could leave it as-is and simply compose the full file path whenever you needed it later. It’s easy enough to write a little function to do that:
#include <linux/limits.h>
#include <string.h>
char *JoinDirAndFileName(const char *dirname, const char *filename){
char path[PATH_MAX] = "";
strcpy(path, dirname);
strcat(path, "/");
strcat(path, filename);
return strdup(path);
}
This, of course, requires the dirname
argument to be a full path name itself, lol.
Additional useful information can be obtained about the files using the stat()
family of functions. For example, we might like to know whether a filename refers to a directory or not:
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
bool isDirectory(const char *filename){
struct stat sb;
if (stat( filename, &sb )) return false; // Failure == not a directory
return (sb.st_mode & S_IFMT) == S_IFDIR;
}
Keep in mind that this requires either:
filename
is an absolute path name for the file (which in our case it is not), orfilename
is relative, meaning the file is in the current working directory (which in our case it is)You may wish to use that information to do things like sort the filenames.
#include <stdlib.h>
#include <string.h>
int FileNameCompare(const void *a, const void *b){
// Sort directory names before filenames
// Then sort using strcmp()
bool is_dir_a = isDirectory(*(const char **)a);
bool is_dir_b = isDirectory(*(const char **)b);
if (is_dir_a == is_dir_b)
return strcmp(*(const char **)a, *(const char **)b);
return is_dir_a ? -1 : 1;
}
void SortFileNames(char **filenames){
qsort(filenames, countFileNames(filenames), sizeof(char *), FileNameCompare);
}
Again, the sort only works if isDirectory()
gets a filename it can use stat()
on, meaning either
Now we can put it all together as an example program:
#include <stdio.h>
int main(void){
// Get the list of filenames in the CWD
char **filenames = getFileNames(".");
if (!filenames)
{
fprintf( stderr, "%s\n", "fooey!" );
return 1;
}
// Might as well sort them
SortFileNames(filenames);
// Print the list
puts("FILENAMES:");
for (size_t n = 0; filenames[n]; n++)
if (isDirectory(filenames[n]))
printf(" %s/\n", filenames[n]);
else
printf(" %s\n", filenames[n]);
// Clean up
filenames = freeFileNames(filenames);
return 0;
}
To compile and run the example program, just concatenate all the code blocks into a text file and save it as dirlist.c
, then compile it with something like:
$ clang -Wall -Wextra -O3 dirlist.c -o dirlist
$ ./dirlist
FILENAMES:
./
../
dirlist
dirlist.c
$
Alas, Windows users, getting a directory’s content requires different functions, but it all works very much the same way. You can read more starting with some convenient Microsoft documentation: FindFirstFile(), FindNextFile(), FindClose(), and other File Management Functions.