web-dev-qa-db-fra.com

Comment créer une arborescence de répertoires en C ++ / Linux?

Je veux un moyen facile de créer plusieurs répertoires en C++/Linux.

Par exemple, je veux enregistrer un fichier lola.file dans le répertoire:

/tmp/a/b/c

mais si les répertoires ne sont pas là, je veux qu'ils soient créés automatiquement. Un exemple de travail serait parfait.

99
Lipis

Voici une fonction C qui peut être compilée avec des compilateurs C++.

/*
@(#)File:           $RCSfile: mkpath.c,v $
@(#)Version:        $Revision: 1.13 $
@(#)Last changed:   $Date: 2012/07/15 00:40:37 $
@(#)Purpose:        Create all directories in path
@(#)Author:         J Leffler
@(#)Copyright:      (C) JLSS 1990-91,1997-98,2001,2005,2008,2012
*/

/*TABSTOP=4*/

#include "jlss.h"
#include "emalloc.h"

#include <errno.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /* HAVE_UNISTD_H */
#include <string.h>
#include "sysstat.h"    /* Fix up for Windows - inc mode_t */

typedef struct stat Stat;

#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
const char jlss_id_mkpath_c[] = "@(#)$Id: mkpath.c,v 1.13 2012/07/15 00:40:37 jleffler Exp $";
#endif /* lint */

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>

/*
** 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 vérifiant les erreurs de strdup() et free(), déclarées dans emalloc.h (Et implémentées dans emalloc.c et estrdup.c). L'en-tête "sysstat.h" Traite des versions brisées de <sys/stat.h> Et peut être remplacé par <sys/stat.h> Sur les systèmes Unix modernes (mais de nombreux problèmes remontaient à 1990). Et "jlss.h" Déclare mkpath().

La modification entre la v1.12 (précédente) et la v1.13 (ci-dessus) correspond au test de EEXIST dans do_mkdir(). Cela a été signalé comme nécessaire par Switch - merci, Switch. Le code de test a été mis à niveau et reproduit le problème sur un MacBook Pro (Intel Core i7 cadencé à 2,3 GHz, exécutant Mac OS X 10.7.4) et suggère que le problème est corrigé dans la révision (mais les tests ne peuvent indiquer que la présence de bogues , jamais leur absence).

(Par la présente, vous êtes autorisé à utiliser ce code dans un but quelconque avec une attribution.)

58
Jonathan Leffler

Facile avec Boost.Filesystem: create_directories

#include <boost/filesystem.hpp>
//...
boost::filesystem::create_directories("/tmp/a/b/c");

Retourne: true si un nouveau répertoire a été créé, sinon false.

154
Benoît
system("mkdir -p /tmp/a/b/c")

est le moyen 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 multi-plateforme mais fonctionnera sous Linux.

36
ChristopheD

Voici mon exemple de code (cela fonctionne 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;
}

Usage:

$ makePath 1/2 folderA/folderB/folderC
creating 1/2 ... OK
creating folderA/folderB/folderC ... OK
24
Maxim Suslov
#include <sys/types.h>
#include <sys/stat.h>

int status;
...
status = mkdir("/tmp/a/b/c", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);

De ici . Vous devrez peut-être séparer les mkdirs de/tmp,/tmp/a,/tmp/a/b/et ensuite/tmp/a/b/c car il n'y a pas d'équivalent de l'indicateur -p dans l'api C. Soyez sûr et ignorez le numéro d'erreur EEXISTS pendant que vous utilisez les niveaux supérieurs.

23
Paul Tomblin

Ceci est similaire au précédent mais fonctionne en avant dans la chaîne plutôt qu'en arrière de manière récursive. Laisse errno avec la bonne valeur pour le dernier échec. S'il y a une barre oblique, il y a un temps supplémentaire dans la boucle qui aurait pu être évité via un find_first_of () en dehors de la boucle ou en détectant l'avance/et en mettant pre à 1. L'efficacité est la même, que nous soyons configurés par un première boucle ou un appel de pré-boucle, et la complexité serait (légèrement) plus élevée lorsqu’on utilise l’appel de pré-boucle.

#include <iostream>
#include <string>
#include <sys/stat.h>

int
mkpath(std::string s,mode_t mode)
{
    size_t pos=0;
    std::string dir;
    int mdret;

    if(s[s.size()-1]!='/'){
        // force trailing / so we can handle everything in loop
        s+='/';
    }

    while((pos=s.find_first_of('/',pos))!=std::string::npos){
        dir=s.substr(0,pos++);
        if(dir.size()==0) continue; // if leading / first time is 0 length
        if((mdret=mkdir(dir.c_str(),mode)) && errno!=EEXIST){
            return mdret;
        }
    }
    return mdret;
}

int main()
{
    int mkdirretval;
    mkdirretval=mkpath("./foo/bar",0755);
    std::cout << mkdirretval << '\n';

}
9
Patrick

Vous avez dit "C++" mais tout le monde ici semble penser à "Bash Shell".

Consultez le code source pour gnu mkdir; alors vous pouvez voir comment implémenter les commandes Shell en C++.

7
Jason Cohen

Il est à noter que l'interface de système de fichiers C++ 17 fait partie de la bibliothèque standard. Cela signifie que l'on peut avoir les éléments suivants pour créer des répertoires:

#include <filesystem>

std::filesystem::create_directories("/a/b/c/d")

Plus d'infos ici: https://en.cppreference.com/w/cpp/filesystem/create_directory

De plus, avec gcc, il faut "-std = c ++ 17" pour CFLAGS. Et "-lstdc ++ fs" à LDLIBS. Ce dernier ne sera potentiellement plus nécessaire dans le futur.

6
mcsim
bool mkpath( std::string path )
{
    bool bSuccess = false;
    int nRC = ::mkdir( path.c_str(), 0775 );
    if( nRC == -1 )
    {
        switch( errno )
        {
            case ENOENT:
                //parent didn't exist, try to create it
                if( mkpath( path.substr(0, path.find_last_of('/')) ) )
                    //Now, try to create again.
                    bSuccess = 0 == ::mkdir( path.c_str(), 0775 );
                else
                    bSuccess = false;
                break;
            case EEXIST:
                //Done!
                bSuccess = true;
                break;
            default:
                bSuccess = false;
                break;
        }
    }
    else
        bSuccess = true;
    return bSuccess;
}
4
Mark

J'ai donc besoin de mkdirp() aujourd'hui, et j'ai trouvé les solutions trop compliquées. C’est pourquoi j’ai écrit un extrait assez court, facile à copier pour les autres qui tombent sur ce fil, nous demandant pourquoi nous avons besoin de tant de lignes de code.

mkdirp.h

#ifndef MKDIRP_H
#define MKDIRP_H

#include <sys/stat.h>

#define DEFAULT_MODE      S_IRWXU | S_IRGRP |  S_IXGRP | S_IROTH | S_IXOTH

/** Utility function to create directory tree */
bool mkdirp(const char* path, mode_t mode = DEFAULT_MODE);

#endif // MKDIRP_H

mkdirp.cpp

#include <errno.h>

bool mkdirp(const char* path, mode_t mode) {
  // const cast for hack
  char* p = const_cast<char*>(path);

  // Do mkdir for each slash until end of string or error
  while (*p != '\0') {
    // Skip first character
    p++;

    // Find first slash or end
    while(*p != '\0' && *p != '/') p++;

    // Remember value from p
    char v = *p;

    // Write end of string at p
    *p = '\0';

    // Create folder from path to '\0' inserted at p
    if(mkdir(path, mode) == -1 && errno != EEXIST) {
      *p = v;
      return false;
    }

    // Restore path to it's former glory
    *p = v;
  }

  return true;
}

Si vous n'aimez pas le const casting et la modification temporaire de la chaîne, faites simplement un strdup() et un free() le ensuite.

3
jonasfj

Je sais que c’est une vieille question, mais les résultats de la recherche sur Google y figurent en bonne place. Les réponses fournies ici ne sont pas vraiment en C++ ou sont un peu trop compliquées.

Veuillez noter que dans mon exemple, createDirTree () est très simple car tout le gros du travail (vérification des erreurs, validation du chemin) doit être effectué par createDir () de toute façon. CreateDir () doit également renvoyer true si le répertoire existe déjà ou si tout ne fonctionne pas.

Voici comment je ferais cela en C++:

#include <iostream>
#include <string>

bool createDir(const std::string dir)
{
    std::cout << "Make sure dir is a valid path, it does not exist and create it: "
              << dir << std::endl;
    return true;
}

bool createDirTree(const std::string full_path)
{
    size_t pos = 0;
    bool ret_val = true;

    while(ret_val == true && pos != std::string::npos)
    {
        pos = full_path.find('/', pos + 1);
        ret_val = createDir(full_path.substr(0, pos));
    }

    return ret_val;
}

int main()
{
    createDirTree("/tmp/a/b/c");
    return 0;
}

Bien sûr, la fonction createDir () sera spécifique au système et il y a déjà suffisamment d'exemples dans d'autres réponses pour l'écrire sous Linux, alors j'ai décidé de l'ignorer.

3
Tom

Étant donné que cet article occupe un rang élevé dans Google pour "Créer une arborescence de répertoires", je vais publier une réponse qui fonctionnera sous Windows. Elle fonctionnera à l'aide de l'API Win32 compilée pour UNICODE ou MBCS. Ceci est porté du code de Mark ci-dessus.

Comme nous travaillons avec Windows, les séparateurs de répertoires sont des barres obliques inversées, pas des barres obliques. Si vous préférez avoir des barres obliques, changez '\\' à '/'

Cela fonctionnera avec:

c:\foo\bar\hello\world

et

c:\foo\bar\hellp\world\

(c'est-à-dire: n'a pas besoin de slash final, vous n'avez donc pas à le vérifier.)

Avant de dire "Utilisez simplement SHCreateDirectoryEx () sous Windows", notez que SHCreateDirectoryEx () est obsolète et peut être supprimé à tout moment des versions futures de Windows.

bool CreateDirectoryTree(LPCTSTR szPathTree, LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL){
    bool bSuccess = false;
    const BOOL bCD = CreateDirectory(szPathTree, lpSecurityAttributes);
    DWORD dwLastError = 0;
    if(!bCD){
        dwLastError = GetLastError();
    }else{
        return true;
    }
    switch(dwLastError){
        case ERROR_ALREADY_EXISTS:
            bSuccess = true;
            break;
        case ERROR_PATH_NOT_FOUND:
            {
                TCHAR szPrev[MAX_PATH] = {0};
                LPCTSTR szLast = _tcsrchr(szPathTree,'\\');
                _tcsnccpy(szPrev,szPathTree,(int)(szLast-szPathTree));
                if(CreateDirectoryTree(szPrev,lpSecurityAttributes)){
                    bSuccess = CreateDirectory(szPathTree,lpSecurityAttributes)!=0;
                    if(!bSuccess){
                        bSuccess = (GetLastError()==ERROR_ALREADY_EXISTS);
                    }
                }else{
                    bSuccess = false;
                }
            }
            break;
        default:
            bSuccess = false;
            break;
    }

    return bSuccess;
}
2
Andy

Si dir n'existe pas, créez-le:

boost::filesystem::create_directories(boost::filesystem::path(output_file).parent_path().string().c_str()); 
1
Frank
mkdir -p /dir/to/the/file

touch /dir/to/the/file/thefile.ending
0
Bigbohne