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::filesystemin 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:
- Open a directory
- Read each entry one by one
- Ignore special entries if needed
- Build your own list or process entries immediately
- 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.
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_nameis 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
-
opendir("test_folder")- The program asks the operating system to open the directory named
test_folder. - If it fails,
dirbecomesNULL.
- The program asks the operating system to open the directory named
-
if (dir == NULL)- This checks whether the directory was opened successfully.
- If not,
perror("opendir")prints the reason.
-
struct dirent *entry;- This declares a pointer that will point to each directory entry returned by
readdir().
- This declares a pointer that will point to each directory entry returned by
-
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
NULLand the loop ends.
-
strcmp(entry->d_name, ".") == 0- Checks whether the current entry is
..
- Checks whether the current entry is
-
strcmp(entry->d_name, "..") == 0- Checks whether the current entry is
...
- Checks whether the current entry is
-
continue;- Skips those special entries and moves to the next one.
-
printf("Found: %s\n", entry->d_name);- Prints the name of the current file or subdirectory.
-
closedir(dir);- Closes the directory and frees associated resources.
If test_folder contains:
notes.txtimage.pngdocs
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
.csvfiles 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.
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
| Approach | Language | Standardized | Platform scope | Ease of use | Notes |
|---|---|---|---|---|---|
opendir / readdir / closedir | C, C++ | POSIX, not ISO C | Unix-like systems | Medium | Common low-level approach |
std::filesystem::directory_iterator | C++17 | Yes, standard C++ | Cross-platform in modern compilers | Easy | Best choice for modern C++ |
Shelling out to ls | Any | No | Depends on shell and OS | Poor | Fragile and discouraged |
| Windows-specific APIs | C, C++ | No | Windows only | Medium to hard | Useful when targeting Windows directly |
readdir vs std::filesystem
| Feature | readdir | std::filesystem |
|---|---|---|
| Works in C | Yes | No |
| Works in modern C++ | Yes | Yes |
| Standard C++ API | No | Yes |
| Simple path operations | Limited | Strong support |
| Cross-platform abstraction | Limited | Better |
| Beginner readability | Lower | Higher |
Which should you choose?
- Use C + POSIX directory functions if you are writing C on Unix-like systems.
- Use
std::filesystemif you are using C++17 or newer. - Avoid parsing
lsoutput.
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 orNULL.entry->d_nameis 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::filesystemin 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.
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
.txtextension.
Keep learning
Related questions
2D Array Loop Order and Cache Performance in C
Learn why swapping nested loops changes 2D array performance in C, using row-major memory layout, cache locality, and practical benchmarks.
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.