system("mkdir -p /tmp/a/b/c")
est le chemin le plus court auquel je puisse penser (en termes de longueur de code, pas nécessairement de temps d'exécution).
Ce n'est pas multiplateforme mais fonctionnera sous Linux.
Facile avec Boost.Filesystem :create_directories
#include <boost/filesystem.hpp>
//...
boost::filesystem::create_directories("/tmp/a/b/c");
Renvoie :true
si un nouveau répertoire a été créé, sinon false
.
Voici mon exemple de code (il fonctionne à la fois pour Windows et 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;
}
Utilisation :
$ makePath 1/2 folderA/folderB/folderC
creating 1/2 ... OK
creating folderA/folderB/folderC ... OK
Avec C++17 ou version ultérieure, il existe l'en-tête standard <filesystem>
avecfonctionstd::filesystem::create_directories
qui devrait être utilisé dans les programmes C++ modernes. Cependant, les fonctions standard C++ n'ont pas l'argument explicitepermissions (mode) spécifique à POSIX.
Cependant, voici une fonction C qui peut être compilée avec des compilateurs 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 */
Les macros STRDUP()
et FREE()
sont des versions de vérification des erreurs de strdup()
et free()
, déclaré en emalloc.h
(et implémenté en emalloc.c
et estrdup.c
).Le "sysstat.h"
l'en-tête traite des versions cassées de <sys/stat.h>
et peut être remplacé par <sys/stat.h>
sur les systèmes Unix modernes (mais il y avait beaucoup de problèmes en 1990). Et "mkpath.h"
déclare mkpath()
.
Le changement entre la v1.12 (version originale de la réponse) et la v1.13 (version modifiée de la réponse) était le test pour EEXIST
endo_mkdir()
.Ceci a été signalé comme nécessaire par Switch — merci, Switch. révision (mais les tests ne peuvent montrer que la présence de bogues, jamais leur absence). Le code affiché est maintenant la v1.16 ; des modifications esthétiques ou administratives ont été apportées depuis la v1.13 (telles que l'utilisation de mkpath.h
au lieu de jlss.h
etinclure <unistd.h>
inconditionnellement dans le code de test uniquement). Il est raisonnable d'affirmer que "sysstat.h"
doit être remplacé par <sys/stat.h>
sauf si vous avez un système inhabituellement récalcitrant.
(Vous êtes par la présente autorisé(e) à utiliser ce code à toute fin avec attribution.)
Ce code est disponible dans mon référentiel SOQ (Stack Overflow Questions) sur GitHub sous forme de fichiers mkpath.c
etmkpath.h
(etc.) dans le sous-répertoire src/so-0067-5039.