Question

How to List Files in a Directory in C and C++

ccppfiledirectory

Question

How can I retrieve the list of files in a directory from within C or C++ code?

I need to do this programmatically, and I cannot run the ls command and parse its output from inside my program.

Short Answer

By the end of this page, you will understand how directory listing works in C and C++, which APIs are commonly used, how to read directory entries safely, and how to handle practical issues such as skipping . and .., checking file types, and writing portable code.

Concept

In C and C++, listing files in a directory usually means using operating system APIs rather than a built-in language feature.

C itself does not provide a standard library function for reading directory contents. Instead, you typically use:

  • POSIX directory functions on Unix-like systems such as Linux and macOS
  • std::filesystem in modern C++ for a higher-level, standard approach
  • Platform-specific APIs on Windows when needed

This matters because real programs often need to:

  • process all files in a folder
  • load configuration files
  • scan logs or images
  • build file indexes
  • validate uploads or backups

The key idea is simple:

  1. Open a directory
  2. Read each entry one by one
  3. Ignore special entries if needed
  4. Build your own list or process entries immediately
  5. Close the directory

In modern C++, std::filesystem is usually the easiest and most readable option. In C, or in older C++ codebases, POSIX functions like opendir(), readdir(), and closedir() are common on Unix-like systems.

Mental Model

Think of a directory as a box of labeled items.

  • Opening the directory is like opening the box.
  • Reading entries is like pulling out one label at a time.
  • Each label tells you the name of something inside.
  • Some labels are special, like . and .., which represent the current and parent directories.
  • Closing the directory is like putting the lid back on when you are done.

In modern C++, std::filesystem acts like a cleaner tool that lets you walk through the box without dealing with as many low-level details.

Take Quiz

Syntax and Examples

C on Unix-like systems with opendir and readdir

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

int main(void) {
    DIR *dir = opendir(".");
    if (dir == NULL) {
        perror("opendir");
        return 1;
    }

    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        printf("%s\n", entry->d_name);
    }

    closedir(dir);
    return 0;
}

What this does

  • opendir(".") opens the current directory.
  • readdir(dir) returns one entry at a time.
  • entry->d_name is the file or directory name.
  • closedir(dir) releases the directory handle.

C++17 with std::filesystem

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    for (const auto& entry : fs::directory_iterator(".")) {
        std::cout << entry.path().filename().string() << '\n';
    }
    return 0;
}

Why this is easier

  • It is part of standard C++17.
  • It is clearer and more portable across platforms.
  • It gives access to paths, file types, sizes, and more.

Skipping . and .. in C

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

int main(void) {
    DIR *dir = opendir(".");
    if (dir == NULL) {
        perror("opendir");
        return 1;
    }

    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }
        printf("%s\n", entry->d_name);
    }

    closedir(dir);
    return 0;
}

This is useful because those names are not regular files you usually want to process.

Step by Step Execution

Consider this C example:

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

int main(void) {
    DIR *dir = opendir("test_folder");
    if (dir == NULL) {
        perror("opendir");
        return 1;
    }

    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }
        printf("Found: %s\n", entry->d_name);
    }

    closedir(dir);
    return 0;
}

Step by step

  1. opendir("test_folder")

    • The program asks the operating system to open the directory named test_folder.
    • If it fails, dir becomes NULL.
  2. if (dir == NULL)

    • This checks whether the directory was opened successfully.
    • If not, perror("opendir") prints the reason.
  3. struct dirent *entry;

    • This declares a pointer that will point to each directory entry returned by readdir().
  4. while ((entry = readdir(dir)) != NULL)

    • readdir() gets the next item in the directory.
    • Each loop iteration processes one item.
    • When there are no more items, it returns NULL and the loop ends.
  5. strcmp(entry->d_name, ".") == 0

    • Checks whether the current entry is ..
  6. strcmp(entry->d_name, "..") == 0

    • Checks whether the current entry is ...
  7. continue;

    • Skips those special entries and moves to the next one.
  8. printf("Found: %s\n", entry->d_name);

    • Prints the name of the current file or subdirectory.
  9. closedir(dir);

    • Closes the directory and frees associated resources.

If test_folder contains:

  • notes.txt
  • image.png
  • docs

The output will be similar to:

Found: notes.txt
Found: image.png
Found: docs

Real World Use Cases

Listing files in a directory is used in many practical situations:

  • Batch processing: read all .csv files in a folder and import them.
  • Media apps: scan image or video directories.
  • Log analysis: find all log files generated today.
  • Backup tools: iterate through files before copying them.
  • Configuration loading: load all config files from a plugin folder.
  • Build tools: detect source files automatically.
  • Server applications: inspect uploaded files in a working directory.

Example in C++ for filtering by extension:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    for (const auto& entry : fs::directory_iterator(".")) {
        if (entry.is_regular_file() && entry.path().extension() == ".txt") {
            std::cout << entry.path().filename().string() << '\n';
        }
    }
}

This pattern is common when processing only certain file types.

Take Quiz

Real Codebase Usage

In real projects, developers rarely just print file names. They usually combine directory iteration with validation and filtering.

Common patterns

Guard clauses

Check early whether a directory can be opened.

DIR *dir = opendir(path);
if (dir == NULL) {
    perror("opendir");
    return 1;
}

This avoids deeply nested code.

Filtering entries

Developers often skip:

  • . and ..
  • hidden files
  • non-regular files
  • files with the wrong extension

Process while iterating

Instead of storing all names first, many programs process each file immediately.

for (const auto& entry : fs::directory_iterator(folder)) {
    if (!entry.is_regular_file()) {
        continue;
    }

    // process file here
}

This can use less memory.

Error handling

File systems are messy. Files may disappear, permissions may change, or symbolic links may behave differently. Real code often checks for errors around each operation.

Recursive scanning

Some applications need subdirectories too. In C++ this often uses recursive_directory_iterator.

for (const auto& entry : fs::recursive_directory_iterator(".")) {
    if (entry.is_regular_file()) {
        // handle nested files
    }
}

Path-aware code

In larger codebases, developers prefer full paths over names alone, because the same filename can exist in different folders.

Common Mistakes

1. Forgetting to close the directory

Broken example:

DIR *dir = opendir(".");
if (dir == NULL) {
    return 1;
}

// read entries
// forgot closedir(dir)

Why it is a problem:

  • It leaks resources.
  • Repeated calls may eventually cause failures.

Fix:

closedir(dir);

2. Assuming every entry is a regular file

Broken example:

for (const auto& entry : std::filesystem::directory_iterator(".")) {
    std::cout << entry.path().filename().string() << '\n';
}

This lists files and directories. If you only want files, check first.

Fix:

for (const auto& entry : std::filesystem::directory_iterator(".")) {
    if (entry.is_regular_file()) {
        std::cout << entry.path().filename().string() << '\n';
    }
}

3. Forgetting about . and .. in C

Broken example:

while ((entry = readdir(dir)) != NULL) {
    printf("%s\n", entry->d_name);
}

This may include special directory entries.

Fix:

if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
    continue;
}

4. Depending on d_type everywhere

Some systems do not fully support reliable file type information in struct dirent. If you need certainty, use a proper status call or std::filesystem helpers.

5. Ignoring errors

Broken example:

for (const auto& entry : std::filesystem::directory_iterator("missing_folder")) {
    std::cout << entry.path() << '\n';
}

If the directory does not exist, this can fail.

Fix:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    try {
        for (const auto& entry : fs::directory_iterator("missing_folder")) {
            std::cout << entry.path().filename().string() << '\n';
        }
    } catch (const fs::filesystem_error& e) {
        std::cerr << e.what() << '\n';
    }
}

6. Using shell commands instead of APIs

Running ls and parsing text output is fragile because:

  • output format can vary
  • filenames may contain spaces or special characters
  • it is less portable
  • it can be less secure

Use directory APIs directly instead.

Comparisons

ApproachLanguageStandardizedPlatform scopeEase of useNotes
opendir / readdir / closedirC, C++POSIX, not ISO CUnix-like systemsMediumCommon low-level approach
std::filesystem::directory_iteratorC++17Yes, standard C++Cross-platform in modern compilersEasyBest choice for modern C++
Shelling out to lsAnyNoDepends on shell and OSPoorFragile and discouraged
Windows-specific APIsC, C++NoWindows onlyMedium to hardUseful when targeting Windows directly

readdir vs std::filesystem

Featurereaddirstd::filesystem
Works in CYesNo
Works in modern C++YesYes
Standard C++ APINoYes
Simple path operationsLimitedStrong support
Cross-platform abstractionLimitedBetter
Beginner readabilityLowerHigher

Which should you choose?

  • Use C + POSIX directory functions if you are writing C on Unix-like systems.
  • Use std::filesystem if you are using C++17 or newer.
  • Avoid parsing ls output.
Take Quiz

Cheat Sheet

C on Unix-like systems

#include <dirent.h>

DIR *dir = opendir(".");
if (dir == NULL) {
    // handle error
}

struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
    // use entry->d_name
}

closedir(dir);

Important points

  • opendir(path) opens a directory.
  • readdir(dir) returns the next entry or NULL.
  • entry->d_name is the entry name.
  • closedir(dir) must be called.
  • Skip . and .. when needed.

C++17

#include <filesystem>
namespace fs = std::filesystem;

for (const auto& entry : fs::directory_iterator(".")) {
    // use entry.path()
}

Useful methods

entry.path().filename()
entry.path().extension()
entry.is_regular_file()
entry.is_directory()

Rules of thumb

  • Prefer std::filesystem in modern C++.
  • Prefer direct APIs over shell commands.
  • Check errors when opening or iterating.
  • Do not assume every entry is a normal file.
  • Use full paths when processing files in real applications.

FAQ

Can standard C list files in a directory by itself?

No. ISO C does not include a standard directory listing API. On Unix-like systems, you usually use POSIX functions such as opendir() and readdir().

What is the easiest way in modern C++?

Use std::filesystem::directory_iterator from C++17.

Why should I not parse ls output?

Because it is fragile, less portable, and can break on filenames with spaces or unusual characters.

How do I skip . and ..?

In C, compare entry->d_name to "." and ".." and skip them. In C++ std::filesystem, these are generally not what you work with directly in the same way.

Can I list only files and not directories?

Yes. In C++ use entry.is_regular_file(). In C, you may need additional checks depending on the platform.

Can I scan subdirectories too?

Yes. In C++ use std::filesystem::recursive_directory_iterator. In C, you would implement recursion manually.

Does this work the same on Windows?

Not always. std::filesystem is the best standard C++ choice for cross-platform code. POSIX functions are mainly for Unix-like systems.

Take Quiz

Mini Project

Description

Build a small program that scans a directory and prints only .txt files. This demonstrates how to iterate through directory entries, filter by file type, and work with filenames safely in a practical task similar to log scanning or batch import jobs.

Goal

Create a program that reads a directory path and prints the names of all regular .txt files inside it.

Requirements

  • Read a directory path from the program.
  • Iterate through the directory entries.
  • Ignore anything that is not a regular file.
  • Print only files with the .txt extension.
Take Quiz

Keep learning

Related questions

View more