Renombrar y mover archivos en C++ con std::filesystem::rename

Para renombrar un archivo en C++ se usa std::filesystem::rename, de C++17. La misma función sirve para moverlo a otra carpeta. Lo importante es cómo te enteras de que algo salió mal, qué pasa si el nombre nuevo ya existe y por qué falla al mover entre discos.

Los ejemplos trabajan dentro de una carpeta salida con tres archivos, informe.txt, notas.txt y viejo.txt, y una subcarpeta archivo. Cada programa la prepara al empezar con una función que no mostramos, para que dé siempre el mismo resultado.

Renombrar con std::filesystem::rename

#include <filesystem>
#include <print>

namespace fs = std::filesystem;

int main()
{
    fs::rename("salida/informe.txt", "salida/informe-2026.txt");
    std::println("existe el viejo: {}", fs::exists("salida/informe.txt"));
    std::println("existe el nuevo: {}", fs::exists("salida/informe-2026.txt"));
}
salidaexiste el viejo: false
existe el nuevo: true

std::filesystem::rename llegó con C++17 y funciona igual con archivos y con carpetas. Si algo falla, lanza std::filesystem::filesystem_error, y el mensaje trae las dos rutas:

#include <filesystem>
#include <print>

namespace fs = std::filesystem;

int main()
{
    try {
        fs::rename("salida/no-existe.txt", "salida/otro.txt");
    } catch (const fs::filesystem_error& e) {
        std::println("{}", e.what());
        std::println("ruta 1: {}", e.path1().string());
    }
}
salidafilesystem error: cannot rename: No such file or directory [salida/no-existe.txt] [salida/otro.txt]
ruta 1: salida/no-existe.txt

Sin excepciones: std::error_code

#include <filesystem>
#include <print>

namespace fs = std::filesystem;

int main()
{
    std::error_code ec;
    fs::rename("salida/no-existe.txt", "salida/otro.txt", ec);
    if (ec) {
        std::println("no se pudo: {} (código {})", ec.message(), ec.value());
    }
}
salidano se pudo: No such file or directory (código 2)

Todas las funciones de std::filesystem tienen una versión que, en lugar de lanzar, deja el error en un std::error_code. Es la cómoda cuando que algo falle es normal, por ejemplo al procesar muchos archivos y querer seguir con el resto.

Si el nombre nuevo ya existe, se pisa sin preguntar

#include <filesystem>
#include <print>
#include <string>

namespace fs = std::filesystem;

int main()
{
    fs::rename("salida/viejo.txt", "salida/notas.txt");
    std::ifstream f("salida/notas.txt");
    std::string contenido;
    std::getline(f, contenido);
    std::println("notas.txt ahora contiene: {}", contenido);
}
salidanotas.txt ahora contiene: notas viejas

En Linux y macOS, renombrar encima de un archivo existente lo sustituye sin avisar: las «notas nuevas» se han perdido. En Windows, el comportamiento depende de la función y de la versión del compilador, así que no conviene contar con él. Si no quieres pisar nada, comprueba antes:

#include <filesystem>
#include <print>

namespace fs = std::filesystem;

int main()
{
    auto renombrar_sin_pisar = [](const fs::path& de, const fs::path& a) {
        if (fs::exists(a)) {
            std::println("{} ya existe: no se renombra", a.string());
            return false;
        }
        fs::rename(de, a);
        return true;
    };
    renombrar_sin_pisar("salida/viejo.txt", "salida/notas.txt");
    renombrar_sin_pisar("salida/viejo.txt", "salida/notas-viejas.txt");
    std::println("existe notas-viejas.txt: {}", fs::exists("salida/notas-viejas.txt"));
}
salidasalida/notas.txt ya existe: no se renombra
existe notas-viejas.txt: true

Ojo: entre el exists y el rename otro programa podría crear el destino. Para un script personal da igual; en un servidor donde varios procesos tocan la misma carpeta, hay que usar mecanismos del sistema operativo (en Linux, renameat2 con RENAME_NOREPLACE).

Mover a otra carpeta

Mover y renombrar son la misma operación: basta con que la ruta nueva esté en otra carpeta.

#include <filesystem>
#include <print>

namespace fs = std::filesystem;

int main()
{
    fs::rename("salida/informe.txt", "salida/archivo/informe.txt");
    for (const auto& e : fs::directory_iterator("salida/archivo")) {
        std::println("en archivo/: {}", e.path().filename().string());
    }
}
salidaen archivo/: informe.txt

Pero solo si las dos rutas están en el mismo sistema de archivos. Entre discos distintos, o entre una carpeta compartida y otra del sistema, rename falla. En el contenedor donde probamos estos ejemplos, la carpeta de trabajo y /tmp están en sistemas de archivos distintos, así que se ve el error real:

#include <filesystem>
#include <print>

namespace fs = std::filesystem;

int main()
{
    std::error_code ec;
    fs::rename("salida/informe.txt", "/tmp/informe.txt", ec);
    std::println("rename: {}", ec ? ec.message() : "bien");

    if (ec == std::errc::cross_device_link) {
        fs::copy_file("salida/informe.txt", "/tmp/informe.txt", fs::copy_options::overwrite_existing);
        fs::remove("salida/informe.txt");
        std::println("copiado y borrado el original: {}", fs::exists("/tmp/informe.txt"));
    }
}
salidarename: Invalid cross-device link
copiado y borrado el original: true

El remedio es lo que hacen el Explorador de Windows o la orden mv: copiar y, cuando la copia está completa, borrar el original.

Cambiar la extensión de varios archivos

#include <filesystem>
#include <print>
#include <algorithm>
#include <string>
#include <vector>

namespace fs = std::filesystem;

int main()
{
    for (const auto& e : fs::directory_iterator("salida")) {
        if (e.is_regular_file() && e.path().extension() == ".txt") {
            fs::path nuevo = e.path();
            nuevo.replace_extension(".md");
            fs::rename(e.path(), nuevo);
        }
    }
    std::vector<std::string> nombres;
    for (const auto& e : fs::directory_iterator("salida")) {
        nombres.push_back(e.path().filename().string());
    }
    std::ranges::sort(nombres);              // el orden del directorio no está garantizado
    std::println("{}", nombres);
}
salida["archivo", "informe.md", "notas.md", "viejo.md"]

directory_iterator devuelve los archivos en el orden en que los guarda el sistema, que no es alfabético: si el resultado tiene que salir ordenado, ordénalo tú. Hay más sobre recorrer carpetas en Carpetas y archivos con std::filesystem.

La función de C: std::rename

#include <filesystem>
#include <print>
#include <cstdio>

namespace fs = std::filesystem;

int main()
{
    if (std::rename("salida/no-existe.txt", "salida/otro.txt") != 0) {
        std::perror("std::rename");
    }
}
salidastd::rename: No such file or directory

Es la que usaba la versión anterior de esta página y sigue funcionando en cualquier compilador de C o de C++: devuelve 0 si fue bien y otra cosa si no, y perror imprime el motivo. La de std::filesystem es preferible porque trabaja con fs::path y da errores más claros.

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.