std::filesystem es la parte de C++ para trabajar con archivos y carpetas sin depender del sistema operativo. Aquí la usamos para recorrer un árbol de carpetas, sumar tamaños, separar las partes de una ruta, copiar, mover y fechar archivos, cambiar permisos y tratar los errores con excepciones o con std::error_code.
También te puede interesar
Recorrer una carpeta y sus subcarpetas
std::filesystem (C++17) trabaja con rutas, carpetas y archivos sin depender del sistema operativo: el mismo código funciona en Linux, macOS y Windows. Todos los ejemplos de esta página crean su propia carpeta de pruebas en el directorio temporal y la borran al terminar. El primero recorre el árbol con recursive_directory_iterator y suma los tamaños:
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <print>
#include <string>
#include <vector>
namespace fs = std::filesystem;
void crear(const fs::path& p, std::size_t bytes)
{
std::ofstream(p) << std::string(bytes, 'x');
}
int main()
{
const fs::path base = fs::temp_directory_path() / "decodigo-fs";
fs::remove_all(base); // por si quedó de otra ejecución
fs::create_directories(base / "docs" / "2026"); // crea también las intermedias
fs::create_directories(base / "img");
crear(base / "leeme.txt", 120);
crear(base / "docs" / "informe.txt", 2'048);
crear(base / "docs" / "2026" / "octubre.csv", 5'300);
crear(base / "img" / "logo.png", 18'000);
crear(base / "img" / "foto.JPG", 250'000);
std::vector<fs::path> archivos;
for (const fs::directory_entry& e : fs::recursive_directory_iterator(base))
if (e.is_regular_file()) archivos.push_back(e.path());
std::ranges::sort(archivos); // el orden del recorrido no está garantizado
std::uintmax_t total = 0;
for (const auto& p : archivos) {
auto bytes = fs::file_size(p);
total += bytes;
std::println("{:>9} {}", bytes, fs::relative(p, base).string());
}
std::println("{} archivos, {:.1f} KB en total", archivos.size(), total / 1024.0);
std::println("borrados: {}", fs::remove_all(base)); // devuelve cuántos elementos borró
}salida 5300 docs/2026/octubre.csv
2048 docs/informe.txt
250000 img/foto.JPG
18000 img/logo.png
120 leeme.txt
5 archivos, 269.0 KB en total
borrados: 9Tres detalles que conviene recordar. create_directories crea todas las carpetas intermedias que falten y no se queja si ya existen. El orden en que el iterador devuelve las entradas depende del sistema de archivos, así que si lo vas a mostrar, ordénalo. Y remove_all borra el árbol entero y devuelve cuántos archivos y carpetas eliminó: 9 aquí, 5 archivos y 4 carpetas, contando la carpeta base.
Las partes de una ruta
Una fs::path sabe separarse en partes, y el operador / une rutas con el separador correcto de cada sistema:
#include <algorithm>
#include <cctype>
#include <filesystem>
#include <print>
#include <string>
namespace fs = std::filesystem;
std::string minusculas(std::string s)
{
std::ranges::transform(s, s.begin(), [](unsigned char c) { return std::tolower(c); });
return s;
}
int main()
{
fs::path p = fs::path("/home/ana") / "fotos" / "verano.2026.JPG";
std::println("ruta completa: {}", p.string());
std::println("carpeta: {}", p.parent_path().string());
std::println("nombre: {}", p.filename().string());
std::println("sin extensión: {}", p.stem().string());
std::println("extensión: {}", p.extension().string());
std::println("¿es absoluta?: {}", p.is_absolute());
// Comparar extensiones sin distinguir mayúsculas
std::println("¿es una foto?: {}", minusculas(p.extension().string()) == ".jpg");
p.replace_extension(".webp");
std::println("convertida: {}", p.string());
// .. y . se resuelven sin tocar el disco
fs::path rara = "/home/ana/fotos/../docs/./cv.pdf";
std::println("normalizada: {}", rara.lexically_normal().string());
}salidaruta completa: /home/ana/fotos/verano.2026.JPG carpeta: /home/ana/fotos nombre: verano.2026.JPG sin extensión: verano.2026 extensión: .JPG ¿es absoluta?: true ¿es una foto?: true convertida: /home/ana/fotos/verano.2026.webp normalizada: /home/ana/docs/cv.pdf
La extensión es solo lo que va tras el último punto: en verano.2026.JPG es .JPG, con el punto incluido y en mayúsculas tal como está escrita. Por eso, para filtrar por tipo, hay que comparar en minúsculas. Y nunca te fíes de la extensión para decidir si un archivo subido por un usuario es seguro: cualquiera puede llamar foto.jpg a un programa.
Copiar, mover y la fecha de modificación
#include <chrono>
#include <filesystem>
#include <format>
#include <fstream>
#include <print>
namespace fs = std::filesystem;
using namespace std::chrono;
int main()
{
const fs::path base = fs::temp_directory_path() / "decodigo-copiar";
fs::remove_all(base);
fs::create_directory(base);
std::ofstream(base / "notas.txt") << "versión 1\n";
// Copiar: falla si el destino existe, salvo que se diga qué hacer
fs::copy_file(base / "notas.txt", base / "copia.txt");
fs::copy_file(base / "notas.txt", base / "copia.txt", fs::copy_options::overwrite_existing);
// Mover o renombrar
fs::create_directory(base / "archivo");
fs::rename(base / "copia.txt", base / "archivo" / "notas-2026.txt");
std::println("¿sigue copia.txt? {}", fs::exists(base / "copia.txt"));
std::println("¿está en archivo/? {}", fs::exists(base / "archivo" / "notas-2026.txt"));
// Fecha de modificación: se fija una para que el ejemplo dé siempre lo mismo
auto cuando = sys_days{2026y / October / 30} + 9h + 15min;
fs::last_write_time(base / "notas.txt", clock_cast<file_clock>(cuando));
auto leida = clock_cast<system_clock>(fs::last_write_time(base / "notas.txt"));
std::println("modificado: {:%Y-%m-%d %H:%M} UTC", floor<minutes>(leida));
fs::remove_all(base);
}salida¿sigue copia.txt? false ¿está en archivo/? true modificado: 2026-10-30 09:15 UTC
Para renombrar un archivo hay un ejemplo dedicado, con todos sus casos de error, en Renombrar un archivo en C++. Ojo con fs::rename entre discos distintos: no puede mover el archivo y falla; en ese caso hay que copiar y después borrar.
Errores: excepciones o error_code
Casi todas las funciones de std::filesystem existen en dos versiones. La normal lanza fs::filesystem_error si algo falla; la que recibe un std::error_code al final no lanza nada y deja el error en esa variable. La segunda es mejor cuando el fallo es algo esperado, como que un archivo no exista:
#include <filesystem>
#include <fstream>
#include <print>
#include <system_error>
namespace fs = std::filesystem;
int main()
{
const fs::path no_existe = "/tmp/decodigo-no-existe.txt";
// 1. Con excepciones
try {
auto bytes = fs::file_size(no_existe);
std::println("mide {} bytes", bytes);
} catch (const fs::filesystem_error& e) {
std::println("excepción: {}", e.what());
std::println(" código: {}", e.code().message());
}
// 2. Con error_code: no lanza
std::error_code ec;
auto bytes = fs::file_size(no_existe, ec);
if (ec)
std::println("error_code: {} (y bytes vale {})", ec.message(), bytes == static_cast<std::uintmax_t>(-1) ? "-1" : "otro valor");
// 3. Algunas funciones avisan devolviendo false
std::println("¿borró algo remove()? {}", fs::remove(no_existe));
// 4. Crear una carpeta donde ya hay un archivo
std::ofstream("/tmp/decodigo-archivo") << "x";
fs::create_directory("/tmp/decodigo-archivo/sub", ec);
std::println("crear carpeta dentro de un archivo: {}", ec.message());
fs::remove("/tmp/decodigo-archivo");
}salidaexcepción: filesystem error: cannot get file size: No such file or directory [/tmp/decodigo-no-existe.txt] código: No such file or directory error_code: No such file or directory (y bytes vale -1) ¿borró algo remove()? false crear carpeta dentro de un archivo: Not a directory
Los mensajes vienen del sistema operativo, por eso salen en inglés en Linux. No hay que comparar el texto, sino el código: ec == std::errc::no_such_file_or_directory funciona en cualquier sistema.
Permisos
fs::permissions cambia los permisos de un archivo y fs::status(p).permissions() los lee. Aquí se quita el permiso de escritura y se comprueba que ya no se puede abrir para escribir:
#include <filesystem>
#include <fstream>
#include <print>
#include <string>
namespace fs = std::filesystem;
std::string rwx(fs::perms p)
{
std::string s;
const fs::perms bits[] = {fs::perms::owner_read, fs::perms::owner_write, fs::perms::owner_exec,
fs::perms::group_read, fs::perms::group_write, fs::perms::group_exec,
fs::perms::others_read, fs::perms::others_write, fs::perms::others_exec};
for (int i = 0; i < 9; ++i)
s += (p & bits[i]) != fs::perms::none ? "rwx"[i % 3] : '-';
return s;
}
int main()
{
const fs::path f = fs::temp_directory_path() / "decodigo-permisos.txt";
std::ofstream(f) << "datos\n";
fs::permissions(f, fs::perms::owner_read | fs::perms::owner_write | fs::perms::group_read);
std::println("permisos: {}", rwx(fs::status(f).permissions()));
fs::permissions(f, fs::perms::owner_write, fs::perm_options::remove); // quitar solo la escritura
std::println("sin escritura: {}", rwx(fs::status(f).permissions()));
std::ofstream salida(f);
std::println("¿se puede abrir para escribir? {}", salida.is_open());
fs::permissions(f, fs::perms::owner_write, fs::perm_options::add); // devolverla
fs::remove(f);
}salidapermisos: rw-r----- sin escritura: r--r----- ¿se puede abrir para escribir? false
/tmp), lo seguro es intentar la operación directamente y tratar el error, en lugar de comprobar antes. Más en Los fallos de seguridad típicos de C++.- ParaFunción
- Saber si existe y qué es
exists,is_regular_file,is_directory,is_symlink - Tamaño de un archivo
file_size - Listar una carpeta
directory_iterator(un nivel) yrecursive_directory_iterator(todo el árbol) - Crear carpetas
create_directory(una) ycreate_directories(con las intermedias) - Copiar, mover, borrar
copy_file,copy,rename,remove,remove_all - Ruta absoluta o relativa
absolute,canonical,relative,lexically_normal - Espacio libre en el disco
space - Carpeta temporal y carpeta actual
temp_directory_pathycurrent_path
Todo el código de esta página se compiló con GCC 15.3 (C++23, -std=c++23 -Wall -Wextra, sin avisos) y se ejecutó en Linux, en un contenedor limpio sin acceso a la red, antes de publicarla; las salidas están copiadas de esa ejecución.