How can I create directory tree in C++/Linux?

Viewed 237667

I want an easy way to create multiple directories in C++/Linux.

For example I want to save a file lola.file in the directory:

/tmp/a/b/c

but if the directories are not there I want them to be created automagically. A working example would be perfect.

18 Answers

Easy with Boost.Filesystem: create_directories

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

Returns: true if a new directory was created, otherwise false.

With C++17 or later, there's the standard header <filesystem> with function std::filesystem::create_directories which should be used in modern C++ programs. The C++ standard functions do not have the POSIX-specific explicit permissions (mode) argument, though.

However, here's a C function that can be compiled with C++ compilers.

/*
@(#)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 */

The macros STRDUP() and FREE() are error-checking versions of strdup() and free(), declared in emalloc.h (and implemented in emalloc.c and estrdup.c). The "sysstat.h" header deals with broken versions of <sys/stat.h> and can be replaced by <sys/stat.h> on modern Unix systems (but there were many issues back in 1990). And "mkpath.h" declares mkpath().

The change between v1.12 (original version of the answer) and v1.13 (amended version of the answer) was the test for EEXIST in do_mkdir(). This was pointed out as necessary by Switch — thank you, Switch. The test code has been upgraded and reproduced the problem on a MacBook Pro (2.3GHz Intel Core i7, running Mac OS X 10.7.4), and suggests that the problem is fixed in the revision (but testing can only show the presence of bugs, never their absence). The code shown is now v1.16; there have been cosmetic or administrative changes made since v1.13 (such as use mkpath.h instead of jlss.h and include <unistd.h> unconditionally in the test code only). It's reasonable to argue that "sysstat.h" should be replaced by <sys/stat.h> unless you have an unusually recalcitrant system.

(You are hereby given permission to use this code for any purpose with attribution.)

This code is available in my SOQ (Stack Overflow Questions) repository on GitHub as files mkpath.c and mkpath.h (etc.) in the src/so-0067-5039 sub-directory.

system("mkdir -p /tmp/a/b/c")

is the shortest way I can think of (in terms of the length of code, not necessarily execution time).

It's not cross-platform but will work under Linux.

#include <sys/types.h>
#include <sys/stat.h>

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

From here. You may have to do separate mkdirs for /tmp, /tmp/a, /tmp/a/b/ and then /tmp/a/b/c because there isn't an equivalent of the -p flag in the C api. Be sure and ignore the EEXISTS errno while you're doing the upper level ones.

It should be noted that starting from C++17 filesystem interface is part of the standard library. This means that one can have following to create directories:

#include <filesystem>

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

More info here: https://en.cppreference.com/w/cpp/filesystem/create_directory

Additionally, with gcc, one needs to "-std=c++17" to CFLAGS. And "-lstdc++fs" to LDLIBS. The latter potentially is not going to be required in the future.

You said "C++" but everyone here seems to be thinking "Bash shell."

Check out the source code to gnu mkdir; then you can see how to implement the shell commands in C++.

So many approaches has been described here but most of them need hard coding of your path into your code. There is an easy solution for that problem, using QDir and QFileInfo, two classes of Qt framework. Since your already in Linux environment it should be easy to use Qt.

QString qStringFileName("path/to/the/file/that/dont/exist.txt");
QDir dir = QFileInfo(qStringFileName).dir();
if(!dir.exists()) {
        dir.mkpath(dir.path());
}

Make sure you have write access to that Path.

Here's C/C++ recursive function that makes use of dirname() to traverse bottom-up the directory tree. It will stop as soon as it finds an existing ancestor.

#include <libgen.h>
#include <string.h>

int create_dir_tree_recursive(const char *path, const mode_t mode)
{
    if (strcmp(path, "/") == 0) // No need of checking if we are at root.
        return 0;

    // Check whether this dir exists or not.
    struct stat st;
    if (stat(path, &st) != 0 || !S_ISDIR(st.st_mode))
    {
        // Check and create parent dir tree first.
        char *path2 = strdup(path);
        char *parent_dir_path = dirname(path2);
        if (create_dir_tree_recursive(parent_dir_path, mode) == -1)
            return -1;

        // Create this dir.
        if (mkdir(path, mode) == -1)
            return -1;
    }

    return 0;
}
mkdir -p /dir/to/the/file

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

If you don't have C++17 yet and look for a platform agnostic solution, use ghc::filesystem. The header-ony code is compatible to C++17 (in fact a backport) and easy to migrate later on.

The others got you the right answer, but I thought I'd demonstrate another neat thing you can do:

mkdir -p /tmp/a/{b,c}/d

Will create the following paths:

/tmp/a/b/d
/tmp/a/c/d

The braces allow you to create multiple directories at once on the same level of the hierarchy, whereas the -p option means "create parent directories as needed".

Related