Introducción
A partir de C++17 se añadirán nuevas abstracciones sobre el sistema de ficheros. De momento están disponibles como parte de las Características Experimentales de C++. Si queréis profundizar aquí está el borrador final de la Especificación Técnica del Sistema de Ficheros.
Comenzar a utilizar característica experimental filesystem C++17 (g++)
Simplemente debemos "decir" al compilador que:
-
estamos escribiendo código C++17 (
-c++1z
) y -
que añada la librería estándar con la librería filesystem (
-lstdc++fs
).
g++ -std=c++1z main.cpp -lstdc++fs && ./a.out
Veamos un ejemplo muy simple utilizando la clase std::filesystem::path
.
#include <experimental/filesystem>
#include <iostream>
namespace fs = std::experimental::filesystem;
using namespace std;
int main()
{
fs::path aPath {"./path/to/file.txt"};
cout << "Parent path: " << aPath.parent_path() << endl;
cout << "Filename: " << aPath.filename() << endl;
cout << "Extension: " << aPath.extension() << endl;
return 0;
}
$ g++ -std=c++1z main.cpp -lstdc++fs && ./a.out
$ ./a.out
Parent path: "./path/to"
Filename: "file.txt"
Extension: ".txt"
Características de filesystem C++17
A continuación vamos a analizar algunas características que nos proporciona std::filesystem con ejemplos en C++11 y C++17, de esta forma podremos hacernos una idea de las utilidades que esta nueva librería nos trae y cómo efectivamente ayuda al desarrollador a escribir código más claro y seguro.
std::filesystem::path
Más arriba ya hemos visto un pequeño ejemplo de uso de clase std::filesystem::path. Ésta abstracción nos proporciona una ruta a ficheros y directorios multi-plataforma, utilizando el separador de directorios correspondiente a la plataforma en la que trabajamos \
en sistemas basados en Windows y /
en sistemas basados en Unix.
Separador de directorios
Si quisiéramos que nuestro software utilizase el separador de directorios correcto para una plataforma, en C++11 podríamos utilizar una macro de compilación condicional:
#include <iostream>
using namespace std;
#ifdef _WIN32
const string SEP = "\\";
#else
const string SEP = "/";
#endif
int main()
{
cout << "Separator in my system " << SEP << endl;
return 0;
}
#include <experimental/filesystem>
#include <iostream>
namespace fs = std::experimental::filesystem;
using namespace std;
int main()
{
cout << "Separator in my system " << fs::path::preferred_separator << endl;
return 0;
}
Operador de separador de directorios
std::filesystem::path implementa el operador /
, el cual nos permite concatenar fácilmente rutas a ficheros o directorios.
Si quisiéramos construir rutas a directorios en C++11, tendríamos que implementar cierta lógica extra para detectar que no añadimos separadores extra y para utilizar el separador correcto:
#include <iostream>
using namespace std;
#ifdef _WIN32
const string SEP = "\\";
#else
const string SEP = "/";
#endif
int main()
{
string root {"/"};
string dir {"var/www/"};
string index {"index.html"};
string pathToIndex{};
pathToIndex.append(root).append(SEP).append(dir).append(SEP).append(index);
cout << pathToIndex << endl;
return 0;
}
//var/www//index.html
Como vemos el resultado no es del todo correcto, deberíamos comprobar si las partes de la ruta ya contienen separador, para no añadirlo.
Toda esta lógica ya está implementada en std::filesystem::path, así que el código en C++17 sería algo así:
#include <experimental/filesystem>
#include <iostream>
namespace fs = std::experimental::filesystem;
using namespace std;
int main()
{
fs::path root {"/"};
fs::path dir {"var/www/"};
fs::path index {"index.html"};
fs::path pathToIndex = root / dir / index;
cout << pathToIndex << endl;
return 0;
}
"/var/www/index.html"
Aquí el código es más limpio y el resultado es simplemente correcto, no hay separadores duplicados.
Crear y borrar directorios
std::filesystem introduce algunas facilidades para crear y borrar directorios y ficheros, primero vamos a ver una de las formas de hacerlo en C++11.
#include <iostream>
#include <cstdio>
#include <sys/stat.h>
using namespace std;
int main()
{
auto opts = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;
mkdir("sandbox", opts);
mkdir("sandbox/a", opts);
mkdir("sandbox/a/b", opts);
mkdir("sandbox/c", opts);
mkdir("sandbox/c/d", opts);
system("ls -la sandbox/*");
remove("sandbox/c/d");
remove("sandbox/a/b");
remove("sandbox/c");
remove("sandbox/a");
remove("sandbox");
system("ls -la");
return 0;
}
g++-4.9 -std=c++11 main.cpp -lm && ./a.out
sandbox/a:
total 12
drwxr-xr-x 3 2001 2000 4096 May 28 12:27 .
drwxr-xr-x 4 2001 2000 4096 May 28 12:27 ..
drwxr-xr-x 2 2001 2000 4096 May 28 12:27 b
sandbox/c:
total 12
drwxr-xr-x 3 2001 2000 4096 May 28 12:27 .
drwxr-xr-x 4 2001 2000 4096 May 28 12:27 ..
drwxr-xr-x 2 2001 2000 4096 May 28 12:27 d
total 8012
drwxrwxrwx 2 2001 2000 4096 May 28 12:27 .
drwxrwxrwx 3 2002 2000 8175616 May 28 12:27 ..
-rwxr-xr-x 1 2001 2000 8168 May 28 12:27 a.out
-rw-rw-rw- 1 2001 2000 517 May 28 12:27 main.cpp
Para crear y borrar directorios anidados, debemos hacerlo uno por uno. Podemos escribir este fragmento de código con menos líneas, pero aún así tendremos que tener cuidado del orden en el que creamos/borramos los directorios.
#include <experimental/filesystem>
#include <iostream>
namespace fs = std::experimental::filesystem;
using namespace std;
int main()
{
fs::create_directories("sandbox/a/b");
fs::create_directories("sandbox/c/d");
system("ls -la sandbox/*");
cout << "Were directories removed? " << fs::remove_all("sandbox") << endl;
system("ls -la");
return 0;
}
g++ -std=c++1z -fconcepts -fgnu-tm -O2 -Wall -Wextra -pedantic -pthread -pedantic-errors main.cpp -lm -latomic -lstdc++fs && ./a.out
sandbox/a:
total 12
drwxr-xr-x 3 2001 2000 4096 May 28 16:45 .
drwxr-xr-x 4 2001 2000 4096 May 28 16:45 ..
drwxr-xr-x 2 2001 2000 4096 May 28 16:45 b
sandbox/c:
total 12
drwxr-xr-x 3 2001 2000 4096 May 28 16:45 .
drwxr-xr-x 4 2001 2000 4096 May 28 16:45 ..
drwxr-xr-x 2 2001 2000 4096 May 28 16:45 d
Were directories removed? 1
total 10132
drwxrwxrwx 2 2001 2000 4096 May 28 16:45 .
drwxrwxrwx 3 2002 2000 8175616 May 28 16:45 ..
-rwxr-xr-x 1 2001 2000 2170976 May 28 16:45 a.out
-rw-rw-rw- 1 2001 2000 393 May 28 16:45 main.cpp
Ejemplo completo: Iterar Recursivamente por Directorios
Vamos a ver un ejemplo algo más completo, consiste en iterar recursivamente a través de directorios, filtrando los ficheros por extension.
C++11
Este es el ejemplo en C++11, sin filtrar por extension, para evitar complicarlo:
#include <dirent.h>
#include <cstring>
#include <iostream>
#include <fstream> // std::ofstream
#include <vector>
#include <memory>
#include <system_error>
#include <sys/stat.h>
using namespace std;
const string UP_DIR = "..";
const string CURRENT_DIR = ".";
const string SEP = "/";
string path(initializer_list<string> parts)
{
string pathTmp {};
string separator = "";
for (auto & part: parts)
{
pathTmp.append(separator).append(part);
separator = SEP;
}
return pathTmp;
}
vector<string> getDirectoryFiles(const string& dir, const vector<string> & extensions)
{
vector<string> files;
shared_ptr<DIR> directory_ptr(opendir(dir.c_str()), [](DIR* dir){ dir && closedir(dir); });
if (!directory_ptr)
{
throw system_error(error_code(errno, system_category()), "Error opening : " + dir);
}
struct dirent *dirent_ptr;
while ((dirent_ptr = readdir(directory_ptr.get())) != nullptr)
{
const string fileName {dirent_ptr->d_name};
if (dirent_ptr->d_type == DT_DIR)
{
if (CURRENT_DIR != fileName && UP_DIR != fileName)
{
auto subFiles = getDirectoryFiles(path({dir, fileName}), extensions);
files.insert(end(files), begin(subFiles), end(subFiles));
}
}
else if (dirent_ptr->d_type == DT_REG)
{
// here we should check also if filename has an extension in extensions vector
files.push_back(path({dir, fileName}));
}
}
return files;
}
int main ()
{
auto opt = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;
mkdir("sandbox", opt);
mkdir("sandbox/a", opt);
mkdir("sandbox/a/b", opt);
vector<string> e_files = {
"./sandbox/a/b/file1.rst",
"./sandbox/a/b/file1.txt",
"./sandbox/a/file2.RST",
"./sandbox/file3.md",
"./sandbox/will_be.ignored"
};
// create files
for (auto &f: e_files)
{
ofstream of(f, ofstream::out);
of << "test";
}
cout << "filtered files: " << endl;
for (auto &f: getDirectoryFiles(".", {".rst", ".RST", ".md"})){
cout << "\t" << f << endl;
}
return 0;
}
g++ -std=c++11 -O2 -Wall -Wextra -pedantic -pthread -pedantic-errors main.cpp -lm -latomic -lstdc++fs && ./a.out
filtered files:
./main.cpp
./sandbox/file3.md
./sandbox/will_be.ignored
./sandbox/a/b/file1.rst
./sandbox/a/b/file1.txt
./sandbox/a/file2.RST
./a.out
C++17
El siguiente ejemplo filtra los ficheros por extension.
#include <dirent.h>
#include <cstring>
#include <iostream>
#include <fstream> // std::ofstream
#include <vector>
#include <memory>
#include <system_error>
#include <sys/stat.h>
using namespace std;
const string UP_DIR = "..";
const string CURRENT_DIR = ".";
const string SEP = "/";
string path(initializer_list<string> parts)
{
string pathTmp {};
string separator = "";
for (auto & part: parts)
{
pathTmp.append(separator).append(part);
separator = SEP;
}
return pathTmp;
}
vector<string> getDirectoryFiles(const string& dir, const vector<string> & extensions)
{
vector<string> files;
shared_ptr<DIR> directory_ptr(opendir(dir.c_str()), [](DIR* dir){ dir && closedir(dir); });
if (!directory_ptr)
{
throw system_error(error_code(errno, system_category()), "Error opening : " + dir);
}
struct dirent *dirent_ptr;
while ((dirent_ptr = readdir(directory_ptr.get())) != nullptr)
{
const string fileName {dirent_ptr->d_name};
if (dirent_ptr->d_type == DT_DIR)
{
if (CURRENT_DIR != fileName && UP_DIR != fileName)
{
auto subFiles = getDirectoryFiles(path({dir, fileName}), extensions);
files.insert(end(files), begin(subFiles), end(subFiles));
}
}
else if (dirent_ptr->d_type == DT_REG)
{
// here we should check also if filename has an extension in extensions vector
files.push_back(path({dir, fileName}));
}
}
return files;
}
int main ()
{
auto opt = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;
mkdir("sandbox", opt);
mkdir("sandbox/a", opt);
mkdir("sandbox/a/b", opt);
vector<string> e_files = {
"./sandbox/a/b/file1.rst",
"./sandbox/a/b/file1.txt",
"./sandbox/a/file2.RST",
"./sandbox/file3.md",
"./sandbox/will_be.ignored"
};
// create files
for (auto &f: e_files)
{
ofstream of(f, ofstream::out);
of << "test";
}
cout << "filtered files: " << endl;
for (auto &f: getDirectoryFiles(".", {".rst", ".RST", ".md"})){
cout << "\t" << f << endl;
}
return 0;
}
g++ -std=c++11 -O2 -Wall -Wextra -pedantic -pthread -pedantic-errors main.cpp -lm -latomic -lstdc++fs && ./a.out
filtered files:
./main.cpp
./sandbox/file3.md
./sandbox/will_be.ignored
./sandbox/a/b/file1.rst
./sandbox/a/b/file1.txt
./sandbox/a/file2.RST
./a.out