Last Update:
22 Common Filesystem Tasks in C++20
Table of Contents
Working with the filesystem can be a daunting task, but it doesn’t have to be. In this post, I’ll walk you through some of the most common filesystem operations using the powerful features introduced in C++17, as well as some new enhancements in C++20/23. Whether you’re creating directories, copying files, or managing permissions, these examples will help you understand and efficiently utilize the std::filesystem
library.
Let’s start.
Directory Operations
1. Creating Directories
Creating directories is a basic yet essential operation. The std::filesystem
library makes this straightforward with the create_directory
function.
#include <filesystem>
#include <iostream>
int main() {
std::filesystem::path dir = "example_directory";
if (std::filesystem::create_directory(dir))
std::cout << "Directory created successfully\n";
else
std::cout << "Failed to create directory\n";
}
The function returns true
when the directory was newly created. But it can also throw an exception in case of an error. You can use noexcept
version of this function:
bool create_directory( const std::filesystem::path& p, std::error_code& ec ) noexcept;
For example:
#include <filesystem>
#include <iostream>
int main() {
std::filesystem::path dir = "/abc/cde"; // recursive?
std::error_code ec{};
if (std::filesystem::create_directory(dir, ec))
std::cout << "Directory created successfully\n";
else
{
std::cout << "Failed to create directory\n";
std::cout << ec.message() << '\n';
}
}
Possible output:
Failed to create directory: No such file or directory
...
See more @Cppreference
2. Creating Nested Directories
When you need to create multiple nested directories, use the create_directories
function.
This function creates directories recursively.
#include <filesystem>
#include <iostream>
#include <exception>
int main() {
std::filesystem::path nested = "a/b/c";
try {
if (std::filesystem::create_directories(nested))
std::cout << "Nested directories created successfully\n";
else
std::cout << "Failed to create nested directories\n";
}
catch (const std::exception& ex) {
std::cout << ex.what() << '\n';
}
}
Run @Compiler Explorer.
3. Removing a Directory
So far, our example directories were left in a vacuum (and I hope Compiler Explorer can safely remove them :), but we can also do it manually from code: We just need the remove
function for this task.
#include <filesystem>
#include <iostream>
#include <exception>
void ls() {
...
}
int main() {
std::filesystem::path dir = "test";
try {
if (std::filesystem::create_directory(dir))
std::cout << "Directory created successfully\n";
else
std::cout << "Failed to create directory\n";
ls();
if (std::filesystem::remove(dir))
std::cout << "Directory removed\n";
else
std::cout << "Failed to remove the directory...\n";
ls();
}
catch (const std::exception& ex) {
std::cout << ex.what() << '\n';
}
}
Possible output @Compiler Explorer:
Directory created successfully
"./compilation-result.json"
"./output.s"
"./example.cpp"
"./test"
Directory removed
"./compilation-result.json"
"./output.s"
"./example.cpp"
4. Removing All Contents of a Directory
To remove a directory along with all its contents, use the remove_all
function.
#include <filesystem>
#include <iostream>
#include <exception>
#include <format>
void ls() {
for (const auto& entry : std::filesystem::recursive_directory_iterator("."))
std::cout << entry.path() << '\n';
}
int main() {
std::filesystem::path dir = "test";
std::filesystem::path nested = dir / "a/b";
std::filesystem::path more = dir / "x/y";
try {
if (std::filesystem::create_directories(nested) &&
std::filesystem::create_directories(more))
std::cout << "Directories created successfully\n";
else
std::cout << "Failed to create directories...\n";
ls();
const auto cnt = std::filesystem::remove_all(dir);
std::cout << std::format("removed {} items\n", cnt);
ls();
}
catch (const std::exception& ex) {
std::cout << ex.what() << '\n';
}
}
Possible output @Compiler Explorer:
Directories created successfully
"./compilation-result.json"
"./output.s"
"./example.cpp"
"./test"
"./test/x"
"./test/x/y"
"./test/a"
"./test/a/b"
removed 5 items
"./compilation-result.json"
"./output.s"
"./example.cpp"
Notice that we had test/x/y
, test/x
, test/a/b
, test/a
and test
directories to remove in this case.
5. Listing Directory Contents
In my previous examples, I’ve used the magical ls()
function, but the implementation is quite simple. It uses directory_iterator
to iterate through the directory:
#include <filesystem>
#include <iostream>
#include <exception>
#include <format>
void ls() {
for (const auto& entry : std::filesystem::directory_iterator("."))
std::cout << entry.path() << '\n';
}
int main() {
ls();
}
The iterator takes the path
object and iterates through files in unspecified order (system dependent).
Here’s a possible output @Compiler Explorer:
"./compilation-result.json"
"./output.s"
"./example.cpp"
6. Listing Directory Contents Recursively
We can use a recursive version of the directory_iterator
to iterate through a whole subtree:
#include <filesystem>
#include <iostream>
void ls() {
for (const auto& entry : std::filesystem::recursive_directory_iterator("."))
std::cout << entry.path() << '\n';
}
int main() {
std::filesystem::create_directories("a/b/c");
std::filesystem::create_directories("x/y/z");
ls();
}
Run @Compiler Explorer. Here’s a possible output:
"./compilation-result.json"
"./output.s"
"./x"
"./x/y"
"./x/y/z"
"./example.cpp"
"./a"
"./a/b"
"./a/b/c"
If we need a nicer output, we can try implementing our own recursive version:
#include <filesystem>
#include <iostream>
void ls(const std::filesystem::path& p, unsigned tabs = 0) {
for (const auto& entry : std::filesystem::directory_iterator(p)) {
if (tabs > 0) std::cout << std::string(tabs, '-');
std::cout << entry.path().stem() << '\n';
if (entry.is_directory())
ls(entry.path(), tabs + 1);
}
}
int main() {
std::filesystem::create_directories("a/b/c");
std::filesystem::create_directories("x/y/z");
ls(".");
}
In this version, I used the is_directory()
member function of the directory_entry
to check if the item is another directory.
The output looks a bit better:
"compilation-result"
"output"
"x"
-"y"
--"z"
"example"
"a"
-"b"
--"c"
7. Creating and Using a Temporary Directory
Thus far, I have used the current directory to create new files and test dirs, but the std::filesystem
library provides facilities to create and manage temporary directories in a better way. It all comes down to the temp_directory_path()
function:
#include <filesystem>
#include <iostream>
#include <fstream>
void create_temp_file(const std::filesystem::path& dir, const std::string& filename) {
std::filesystem::path file_path = dir / filename;
std::ofstream(file_path) << "This is a temporary file.";
std::cout << "Temporary file created at: " << file_path << '\n';
}
int main() {
std::filesystem::path temp_dir = std::filesystem::temp_directory_path() / "my_temp_directory";
std::filesystem::create_directory(temp_dir);
std::cout << "Temporary directory created at: " << temp_dir << '\n';
create_temp_file(temp_dir, "temp_file.txt");
std::cout << "Contents of the temporary directory:\n";
for (const auto& entry : std::filesystem::directory_iterator(temp_dir)) {
std::cout << entry.path() << '\n';
}
}
Possible output:
Temporary directory created at: "/tmp/my_temp_directory"
Temporary file created at: "/tmp/my_temp_directory/temp_file.txt"
Contents of the temporary directory:
"/tmp/my_temp_directory/temp_file.txt"
On Windows (Running and compiling from Visual Studio) I’m getting the following output:
Temporary directory created at: "C:\\Users\\Admin\\AppData\\Local\\Temp\\my_temp_directory"
Temporary file created at: "C:\\Users\\Admin\\AppData\\Local\\Temp\\my_temp_directory\\temp_file.txt"
Contents of the temporary directory:
"C:\\Users\\Admin\\AppData\\Local\\Temp\\my_temp_directory\\temp_file.txt"
File Operations
7. Copying Files
Copying files is a common task, especially when working with backups or file distribution. Use the copy
function to achieve this.
#include <filesystem>
#include <iostream>
int main() {
std::filesystem::path src = "source_file.txt";
std::filesystem::path dest = "destination_file.txt";
try {
std::filesystem::copy(src, dest);
std::cout << "File copied successfully\n";
} catch (std::filesystem::filesystem_error& e) {
std::cout << e.what() << '\n';
}
}
The code won’t run, as we have no files:
filesystem error: cannot copy: No such file or directory [source_file.txt] [destination_file.txt]
8. Copying Files Recursively
Copying directories, including their contents, can be done using the copy
function with recursive options.
#include <filesystem>
#include <iostream>
#include <fstream>
void create_temp_directories_and_files() {
std::filesystem::create_directories("source_directory/subdir1");
std::filesystem::create_directories("source_directory/subdir2");
std::ofstream("source_directory/file1.txt") << "This is file 1";
std::ofstream("source_directory/subdir1/file2.txt") << "This is file 2";
std::ofstream("source_directory/subdir2/file3.txt") << "This is file 3";
}
int main() {
create_temp_directories_and_files();
std::filesystem::path src = "source_directory";
std::filesystem::path dest = "destination_directory";
try {
std::filesystem::copy(src, dest, std::filesystem::copy_options::recursive);
std::cout << "Directory copied successfully\n";
} catch (std::filesystem::filesystem_error& e) {
std::cout << e.what() << '\n';
}
for (const auto& entry : std::filesystem::recursive_directory_iterator(dest)) {
std::cout << entry.path() << '\n';
}
}
The interesting part is that there’s a function overload which takes std::filesystem::copy_options
:
enum class copy_options {
none = /* unspecified */,
skip_existing = /* unspecified */,
overwrite_existing = /* unspecified */,
update_existing = /* unspecified */,
recursive = /* unspecified */,
copy_symlinks = /* unspecified */,
skip_symlinks = /* unspecified */,
directories_only = /* unspecified */,
create_symlinks = /* unspecified */,
create_hard_links = /* unspecified */
};
See more examples @CppReference
9. Moving and Renaming Files
Moving and renaming files or directories can be done using the rename
function.
#include <filesystem>
#include <iostream>
#include <fstream>
void ls() {
for (const auto& entry : std::filesystem::directory_iterator("."))
std::cout << entry.path() << '\n';
}
int main() {
std::ofstream("old_file.txt") << "This is file 1";
std::filesystem::path old_name = "old_file.txt";
std::filesystem::path new_name = "new_file.txt";
try {
ls();
std::filesystem::rename(old_name, new_name);
std::cout << "File renamed/moved successfully\n";
ls();
} catch (std::filesystem::filesystem_error& e) {
std::cout << e.what() << '\n';
}
}
10. Creating Hard Links
Hard links provide multiple directory entries for a single file. Use the create_hard_link
function to create them.
#include <filesystem>
#include <iostream>
#include <fstream>
#include <format>
void ls() {
for (const auto& entry : std::filesystem::directory_iterator("."))
std::cout << std::format("{}, link count {}\n", entry.path().c_str(), entry.hard_link_count());
}
int main() {
std::ofstream("target_file.txt") << "This is file 1";
std::filesystem::path target = "target_file.txt";
std::filesystem::path link = "hard_link_file.txt";
try {
std::filesystem::create_hard_link(target, link);
std::cout << "Hard link created successfully\n";
ls();
} catch (std::filesystem::filesystem_error& e) {
std::cout << e.what() << '\n';
}
}
Possible output:
Hard link created successfully
./compilation-result.json, link count 1
./target_file.txt, link count 2
./hard_link_file.txt, link count 2
./output.s, link count 1
./example.cpp, link count 1
11. Creating Symbolic Links
Symbolic links (symlinks) are another way to reference files. Use the create_symlink
function for this.
unix - What is the difference between a symbolic link and a hard link? - Stack Overflow
#include <filesystem>
#include <iostream>
#include <fstream>
void display_file_content(const std::filesystem::path& path) {
if (std::filesystem::exists(path)) {
std::ifstream file(path);
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
std::cout << "Content of " << path << ": " << content << '\n';
} else {
std::cout << path << " does not exist.\n";
}
}
int main() {
std::filesystem::path original_file = "original_file.txt";
std::filesystem::path symlink = "symlink_to_file.txt";
std::filesystem::path hardlink = "hardlink_to_file.txt";
// Step 1: Create the original file
std::ofstream(original_file) << "Hello World!";
std::cout << "Original file created.\n";
display_file_content(original_file);
// Step 2: Create a symbolic link to the original file
try {
std::filesystem::create_symlink(original_file, symlink);
std::cout << "Symbolic link created successfully.\n";
display_file_content(symlink);
} catch (std::filesystem::filesystem_error& e) {
std::cout << e.what() << '\n';
}
// Step 3: Create a hard link to the original file
try {
std::filesystem::create_hard_link(original_file, hardlink);
std::cout << "Hard link created successfully.\n";
display_file_content(hardlink);
} catch (std::filesystem::filesystem_error& e) {
std::cout << e.what() << '\n';
}
// Step 4: Delete the original file and compare...
std::filesystem::remove(original_file);
std::cout << "Original file deleted.\n";
display_file_content(symlink);
display_file_content(hardlink);
}
Possible output:
Original file created.
Content of "original_file.txt": Hello World!
Symbolic link created successfully.
Content of "symlink_to_file.txt": Hello World!
Hard link created successfully.
Content of "hardlink_to_file.txt": Hello World!
Original file deleted.
"symlink_to_file.txt" does not exist.
Content of "hardlink_to_file.txt": Hello World!
Path and Existence Checks
12. Checking File or Directory Existence
Before performing operations on files or directories, it’s often necessary to check their existence using the exists
function.
#include <filesystem>
#include <iostream>
int main() {
std::filesystem::path p = "example_file.txt";
if (std::filesystem::exists(p))
std::cout << "File or directory exists\n";
else
std::cout << "File or directory does not exist\n";
}
13. Checking if a Path is a File or Directory
Differentiating between files and directories is crucial for many file processing tasks. The is_regular_file
and is_directory
functions help with this.
#include <filesystem>
#include <iostream>
int main() {
std::filesystem::path p = "example_path";
if (std::filesystem::is_regular_file(p))
std::cout << "It's a file\n";
else if (std::filesystem::is_directory(p))
std::cout << "It's a directory\n";
else
std::cout << "It's neither a regular file nor a directory\n";
}
14. Reading Symlink Status
If your application deals with symbolic links, you may need to read the status of these links using the is_symlink
function. To get the target file, you can use read_symlink()
:
#include <filesystem>
#include <iostream>
#include <fstream>
int main() {
std::filesystem::path original_file = "original_file.txt";
std::filesystem::path symlink = "symlink_to_file.txt";
std::ofstream(original_file) << "Hello World!";
std::cout << "Original file created.\n";
try {
std::filesystem::create_symlink(original_file, symlink);
std::cout << "Symbolic link created successfully.\n";
} catch (std::filesystem::filesystem_error& e) {
std::cout << e.what() << '\n';
}
if (std::filesystem::is_symlink(symlink)) {
std::cout << symlink << " is a symbolic link.\n";
std::filesystem::path target = std::filesystem::read_symlink(symlink);
std::cout << "It points to: " << target << '\n';
} else {
std::cout << symlink << " is not a symbolic link.\n";
}
}
15. Getting Absolute Path
Getting the absolute path of a relative path is often necessary for various file operations. Use the absolute
function for this.
#include <filesystem>
#include <iostream>
int main() {
// Example relative paths
std::filesystem::path relative_path1 = "example_directory";
std::filesystem::path relative_path2 = "../parent_directory";
std::filesystem::path relative_path3 = "subdir/another_file.txt";
// Convert to absolute paths
std::filesystem::path absolute_path1 = std::filesystem::absolute(relative_path1);
std::filesystem::path absolute_path2 = std::filesystem::absolute(relative_path2);
std::filesystem::path absolute_path3 = std::filesystem::absolute(relative_path3);
// Display the absolute paths
std::cout << "Relative path: " << relative_path1 << " -> Absolute path: " << absolute_path1 << '\n';
std::cout << "Relative path: " << relative_path2 << " -> Absolute path: " << absolute_path2 << '\n';
std::cout << "Relative path: " << relative_path3 << " -> Absolute path: " << absolute_path3 << '\n';
}
Possible output:
Relative path: "example_directory" -> Absolute path: "/app/example_directory"
Relative path: "../parent_directory" -> Absolute path: "/app/../parent_directory"
Relative path: "subdir/another_file.txt" -> Absolute path: "/app/subdir/another_file.txt"
16. Getting Relative Path
Sometimes, you need to convert an absolute path to a relative path. The relative
function can be used for this purpose.
#include <filesystem>
#include <iostream>
int main() {
std::filesystem::path base_path = "/home/user";
std::filesystem::path absolute_path = "/home/user/example_directory/file.txt";
std::filesystem::path relative_path = std::filesystem::relative(absolute_path, base_path);
std::cout << "Relative path: " << relative_path << '\n';
}
17. Displaying paths without quotes
Have you noticed that when I print out a path object, it always has quotes around it?
According to CppReference the stream operator for the path
class is defined in the following way:
Performs stream input or output on the path p.
std::quoted
is used so that spaces do not cause truncation when later read by stream input operator.
To remove the quotes, we just need to get the raw string that represents the path. For example:
void ls() {
for (const auto& entry : std::filesystem::directory_iterator("."))
std::cout << entry.path().c_str() << '\n';
}
So the output can be:
./compilation-result.json
./output.s
./example.cpp
Rather than:
"./compilation-result.json"
"./output.s"
"./example.cpp"
Miscellaneous Operations
18. Calculating Directory Size
Calculating the total size of all files within a directory can be useful for disk usage analysis and cleanup tasks.
#include <filesystem>
#include <iostream>
#include <fstream>
// Function to create a test directory with some files and subdirectories
void create_test_directory(const std::filesystem::path& dir) {
std::filesystem::create_directories(dir / "subdir1");
std::filesystem::create_directories(dir / "subdir2");
std::ofstream(dir / "file1.txt") << "ABC";
std::ofstream(dir / "subdir1/file2.txt") << "XYZ";
std::ofstream(dir / "subdir2/file3.txt") << "123";
}
// Function to calculate the total size of a directory
std::uintmax_t calculate_directory_size(const std::filesystem::path& dir) {
std::uintmax_t size = 0;
for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) {
if (std::filesystem::is_regular_file(entry.path())) {
size += std::filesystem::file_size(entry.path());
}
}
return size;
}
int main() {
// Create a test directory with some files and subdirectories
std::filesystem::path test_dir = "test_directory";
create_test_directory(test_dir);
std::cout << "Test directory created.\n";
// Calculate the total size of the test directory
std::uintmax_t total_size = calculate_directory_size(test_dir);
std::cout << "Total size of directory " << test_dir << ": " << total_size << " bytes\n";
// Clean up by removing the test directory and its contents
std::filesystem::remove_all(test_dir);
std::cout << "Test directory removed.\n";
}
Do you know what the size of that test directory will be?
19. Determining Free Space on a Filesystem
Knowing the available free space on a filesystem is important for managing storage and preventing errors due to insufficient disk space. The space()
function returns a space_info
object that contains information about the free space, available space, and capacity of the filesystem where a given path is located.
#include <filesystem>
#include <iostream>
int main() {
std::filesystem::path p = "/";
auto space_info = std::filesystem::space(p);
std::cout << "Free space: " << space_info.free << " bytes\n";
std::cout << "Available space: " << space_info.available << " bytes\n";
std::cout << "Capacity: " << space_info.capacity << " bytes\n";
}
20. Checking File Permissions
File permissions determine who can read, write, or execute a file. Use the status
function to check these permissions.
#include <filesystem>
#include <iostream>
int main() {
std::filesystem::path file = "example_file.txt";
std::filesystem::perms p = std::filesystem::status(file).permissions();
std::cout << "Permissions for " << file << ":\n";
std::cout << ((p & std::filesystem::perms::owner_read) != std::filesystem::perms::none ? "r" : "-")
<< ((p & std::filesystem::perms::owner_write) != std::filesystem::perms::none ? "w" : "-")
<< ((p & std::filesystem::perms::owner_exec) != std::filesystem::perms::none ? "x" : "-")
<< '\n';
}
21. Setting File Permissions
Changing file permissions can be necessary for securing files or enabling certain operations. Use the permissions
function to set these permissions.
#include <filesystem>
#include <iostream>
#include <fstream>
void create_test_file(const std::filesystem::path& path) {
std::ofstream(path) << "This is a test file.";
std::cout << "Test file created at: " << path << '\n';
}
void display_permissions(const std::filesystem::path& path) {
std::filesystem::perms p = std::filesystem::status(path).permissions();
std::cout << "Permissions for " << path << ": "
<< ((p & std::filesystem::perms::owner_read) != std::filesystem::perms::none ? "r" : "-")
<< ((p & std::filesystem::perms::owner_write) != std::filesystem::perms::none ? "w" : "-")
<< ((p & std::filesystem::perms::owner_exec) != std::filesystem::perms::none ? "x" : "-")
<< ((p & std::filesystem::perms::group_read) != std::filesystem::perms::none ? "r" : "-")
<< ((p & std::filesystem::perms::group_write) != std::filesystem::perms::none ? "w" : "-")
<< ((p & std::filesystem::perms::group_exec) != std::filesystem::perms::none ? "x" : "-")
<< ((p & std::filesystem::perms::others_read) != std::filesystem::perms::none ? "r" : "-")
<< ((p & std::filesystem::perms::others_write) != std::filesystem::perms::none ? "w" : "-")
<< ((p & std::filesystem::perms::others_exec) != std::filesystem::perms::none ? "x" : "-")
<< '\n';
}
int main() {
std::filesystem::path test_file = "test_file.txt";
create_test_file(test_file);
std::cout << "Initial ";
display_permissions(test_file);
// Set new permissions for the test file
std::filesystem::perms new_perms = std::filesystem::perms::owner_read |
std::filesystem::perms::owner_write |
std::filesystem::perms::owner_exec;
try {
std::filesystem::permissions(test_file, new_perms);
std::cout << "Permissions changed successfully\n";
} catch (const std::filesystem::filesystem_error& e) {
std::cout << e.what() << '\n';
}
std::cout << "Updated ";
display_permissions(test_file);
std::filesystem::remove(test_file);
std::cout << "Test file removed.\n";
}
Possible output:
Test file created at: "test_file.txt"
Initial Permissions for "test_file.txt": rw-rw-r--
Permissions changed successfully
Updated Permissions for "test_file.txt": rwx------
Test file removed.
22. Listing Files in Last Modification Time Order
Sorting and listing files based on their last modification time can be useful for many applications, such as finding the most recently modified files. The following example demonstrates how to list files in a directory sorted by their last modification time.
#include <iostream>
#include <fstream>
#include <filesystem>
#include <format>
#include <map>
#include <algorithm>
#include <chrono>
#include <thread>
namespace fs = std::filesystem;
int main() {
std::map<fs::file_time_type, fs::path> files;
std::vector<fs::path> toDelete;
// create some files...
for (int i = 0; i < 5; ++i)
{
toDelete.emplace_back(std::format("example{}.txt", i));
std::ofstream(toDelete.back().c_str()) << "Hello, World!";
std::this_thread::sleep_for(std::chrono::seconds(1));
}
for (const auto& entry : fs::directory_iterator(fs::current_path())) {
if (entry.is_regular_file())
files.emplace(fs::last_write_time(entry), entry.path());
}
for (const auto& [time, path] : files)
std::cout << std::format("{0:%X} on {0:%F}, {1}\n", time, path.string());
for (auto& f : toDelete)
fs::remove(f);
}
Summary
In this blog post, we’ve explored a variety of essential filesystem operations using the std::filesystem
library in C++17, C++20, and C++23. From creating directories and files to managing symbolic and hard links, and from checking file permissions to calculating directory sizes, we’ve covered the fundamental tasks you need to handle the filesystem efficiently.
I hope this guide has provided you with valuable insights and practical examples to help you navigate the complexities of filesystem operations in C++.
Back to you
- Do you use
std::filesystem
? - What other operations are useful when working with the filesystem?
Share your comment below.
I've prepared a valuable bonus if you're interested in Modern C++!
Learn all major features of recent C++ Standards!
Check it out here: