system("mkdir -p /tmp/a/b/c")
es la forma más corta que se me ocurre (en términos de la longitud del código, no necesariamente el tiempo de ejecución).
No es multiplataforma pero funcionará bajo Linux.
Fácil con Boost.Filesystem:create_directories
#include <boost/filesystem.hpp>
//...
boost::filesystem::create_directories("/tmp/a/b/c");
Devuelve:true
si se creó un nuevo directorio, de lo contrario false
.
Aquí está mi ejemplo de código (funciona tanto para Windows como para Linux):
#include <iostream>
#include <string>
#include <sys/stat.h> // stat
#include <errno.h> // errno, ENOENT, EEXIST
#if defined(_WIN32)
#include <direct.h> // _mkdir
#endif
bool isDirExist(const std::string& path)
{
#if defined(_WIN32)
struct _stat info;
if (_stat(path.c_str(), &info) != 0)
{
return false;
}
return (info.st_mode & _S_IFDIR) != 0;
#else
struct stat info;
if (stat(path.c_str(), &info) != 0)
{
return false;
}
return (info.st_mode & S_IFDIR) != 0;
#endif
}
bool makePath(const std::string& path)
{
#if defined(_WIN32)
int ret = _mkdir(path.c_str());
#else
mode_t mode = 0755;
int ret = mkdir(path.c_str(), mode);
#endif
if (ret == 0)
return true;
switch (errno)
{
case ENOENT:
// parent didn't exist, try to create it
{
int pos = path.find_last_of('/');
if (pos == std::string::npos)
#if defined(_WIN32)
pos = path.find_last_of('\\');
if (pos == std::string::npos)
#endif
return false;
if (!makePath( path.substr(0, pos) ))
return false;
}
// now, try to create again
#if defined(_WIN32)
return 0 == _mkdir(path.c_str());
#else
return 0 == mkdir(path.c_str(), mode);
#endif
case EEXIST:
// done!
return isDirExist(path);
default:
return false;
}
}
int main(int argc, char* ARGV[])
{
for (int i=1; i<argc; i++)
{
std::cout << "creating " << ARGV[i] << " ... " << (makePath(ARGV[i]) ? "OK" : "failed") << std::endl;
}
return 0;
}
Uso:
$ makePath 1/2 folderA/folderB/folderC
creating 1/2 ... OK
creating folderA/folderB/folderC ... OK
Con C++17 o posterior, existe el encabezado estándar <filesystem>
con funciónstd::filesystem::create_directories
que debería usarse en los programas modernos de C++. Sin embargo, las funciones estándar de C++ no tienen el argumento de permisos explícitos (modo) específico de POSIX.
Sin embargo, aquí hay una función de C que se puede compilar con compiladores de C++.
/*
@(#)File: mkpath.c
@(#)Purpose: Create all directories in path
@(#)Author: J Leffler
@(#)Copyright: (C) JLSS 1990-2020
@(#)Derivation: mkpath.c 1.16 2020/06/19 15:08:10
*/
/*TABSTOP=4*/
#include "posixver.h"
#include "mkpath.h"
#include "emalloc.h"
#include <errno.h>
#include <string.h>
/* "sysstat.h" == <sys/stat.h> with fixup for (old) Windows - inc mode_t */
#include "sysstat.h"
typedef struct stat Stat;
static int do_mkdir(const char *path, mode_t mode)
{
Stat st;
int status = 0;
if (stat(path, &st) != 0)
{
/* Directory does not exist. EEXIST for race condition */
if (mkdir(path, mode) != 0 && errno != EEXIST)
status = -1;
}
else if (!S_ISDIR(st.st_mode))
{
errno = ENOTDIR;
status = -1;
}
return(status);
}
/**
** mkpath - ensure all directories in path exist
** Algorithm takes the pessimistic view and works top-down to ensure
** each directory in path exists, rather than optimistically creating
** the last element and working backwards.
*/
int mkpath(const char *path, mode_t mode)
{
char *pp;
char *sp;
int status;
char *copypath = STRDUP(path);
status = 0;
pp = copypath;
while (status == 0 && (sp = strchr(pp, '/')) != 0)
{
if (sp != pp)
{
/* Neither root nor double slash in path */
*sp = '\0';
status = do_mkdir(copypath, mode);
*sp = '/';
}
pp = sp + 1;
}
if (status == 0)
status = do_mkdir(path, mode);
FREE(copypath);
return (status);
}
#ifdef TEST
#include <stdio.h>
#include <unistd.h>
/*
** Stress test with parallel running of mkpath() function.
** Before the EEXIST test, code would fail.
** With the EEXIST test, code does not fail.
**
** Test shell script
** PREFIX=mkpath.$$
** NAME=./$PREFIX/sa/32/ad/13/23/13/12/13/sd/ds/ww/qq/ss/dd/zz/xx/dd/rr/ff/ff/ss/ss/ss/ss/ss/ss/ss/ss
** : ${MKPATH:=mkpath}
** ./$MKPATH $NAME &
** [...repeat a dozen times or so...]
** ./$MKPATH $NAME &
** wait
** rm -fr ./$PREFIX/
*/
int main(int argc, char **argv)
{
int i;
for (i = 1; i < argc; i++)
{
for (int j = 0; j < 20; j++)
{
if (fork() == 0)
{
int rc = mkpath(argv[i], 0777);
if (rc != 0)
fprintf(stderr, "%d: failed to create (%d: %s): %s\n",
(int)getpid(), errno, strerror(errno), argv[i]);
exit(rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
}
int status;
int fail = 0;
while (wait(&status) != -1)
{
if (WEXITSTATUS(status) != 0)
fail = 1;
}
if (fail == 0)
printf("created: %s\n", argv[i]);
}
return(0);
}
#endif /* TEST */
Las macros STRDUP()
y FREE()
son versiones de comprobación de errores de strdup()
y free()
, declarado en emalloc.h
(e implementado en emalloc.c
y estrdup.c
).El "sysstat.h"
el encabezado trata con versiones rotas de <sys/stat.h>
y puede ser reemplazado por <sys/stat.h>
en los sistemas Unix modernos (pero hubo muchos problemas en 1990). Y "mkpath.h"
declara mkpath()
.
El cambio entre v1.12 (versión original de la respuesta) y v1.13 (versión modificada de la respuesta) fue la prueba para EEXIST
en do_mkdir()
.Switch señaló que esto era necesario. Gracias, Switch. El código de prueba se actualizó y reprodujo el problema en una MacBookPro (2,3 GHz Intel Core i7, con Mac OS X 10.7.4) y sugiere que el problema se solucionó en el revisión (pero las pruebas solo pueden mostrar la presencia de errores, nunca su ausencia). El código que se muestra ahora es v1.16; se han realizado cambios estéticos o administrativos desde v1.13 (como usar mkpath.h
en lugar de jlss.h
e incluir <unistd.h>
incondicionalmente solo en el código de prueba). Es razonable argumentar que "sysstat.h"
debe ser reemplazado por <sys/stat.h>
a menos que tenga un sistema inusualmente recalcitrante.
(Por la presente se le otorga permiso para usar este código para cualquier propósito con atribución).
Este código está disponible en mi repositorio SOQ (Preguntas de desbordamiento de pila) en GitHub como archivos mkpath.c
y mkpath.h
(etc.) en el subdirectorio thesrc/so-0067-5039.