如何在C ++ / Linux中创建目录树?


109

我想要一种简单的方法来在C ++ / Linux中创建多个目录。

例如,我想在目录中保存文件lola.file:

/tmp/a/b/c

但是如果目录不存在,我希望它们可以自动创建。一个可行的例子将是完美的。


C ++不具有任何内置的设施,建立目录树本身。您将必须使用C和系统调用或Boost等外部库。C和系统调用将取决于平台。
jww 2014年

6
@noloader非常感谢..但是我认为四年后我几乎得到了我的答案,因为您可以通过13种不同的方式看到波纹管……
Lipis 2014年

是的,令我惊讶的是,没有人明确声明您无法在C ++中做到这一点(假设您想要一种可在Linux上运行的C ++中的可移植方法)。但您可能知道;)。但是,对于非便携式C代码有很多好的建议。
jww

什么是“ C ++ / Linux”?
Lightness Races in Orbit

3
@LightnessRacesinOrbit这是我在Linux上使用C ++的大学时代:)
Lipis

Answers:


59

在C ++ 17或更高版本中,有<filesystem>带有函数 的标准标头std::filesystem::create_directories ,应在现代C ++程序中使用。但是,C ++标准函数没有特定于POSIX的显式权限(模式)参数。

但是,这是一个可以用C ++编译器编译的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 */

STRDUP()FREE()是的错误检查版本, strdup()并且free()在中声明emalloc.h(并在emalloc.c和中实现 estrdup.c)。该"sysstat.h"标头处理的是旧版本,<sys/stat.h> 可以<sys/stat.h>在现代Unix系统上替换(但是在1990年有很多问题)。并"mkpath.h"声明mkpath()

V1.12(原始版本的答案)和V1.13(答案的修正版)之间的变化是用于测试EEXISTdo_mkdir()Switch指出了这一点, 谢谢您-Switch。测试代码已升级,并在MacBook Pro(2.3GHz Intel Core i7,运行Mac OS X 10.7.4)上重现了该问题,并表明该问题已在修订版中修复(但测试只能显示存在的错误) ,从来没有他们缺席)。现在显示的代码是v1.16;自v1.13起,已经进行了外观或管理上的更改(例如,仅在无条件的情况下使用mkpath.h代替jlss.h和仅包含<unistd.h>在测试代​​码中)。有理由争辩说"sysstat.h"<sys/stat.h>除非您有一个异常顽强的系统,否则应将其替换 。

(特此授权您出于归属将此代码用于任何目的。)

该代码可在 GitHub上的SOQ(堆栈溢出问题)存储库中以文件形式(mkpath.c以及 mkpath.h(等)在 src / so-0067-5039 子目录中)获得。


2
它肯定比系统快。系统涉及很多开销。基本上,该过程必须分叉,然后至少必须加载两个二进制文件(其中一个可能已经在缓存中),另一个就在另一个上面,...
ypnos

1
我忘记了:然后“ mkdir -p”将至少与上面发布的代码相同!
ypnos

7
我实际遇到的这段代码中有一个微妙的竞争条件。仅当多个程序同时启动并创建相同的文件夹路径时,才会发生这种情况。解决方法是if (errno != EEXIST) { status = -1; }在mkdir失败时添加。
切换

2
@Switch:谢谢。那就是使用stat()before 的麻烦mkdir();这是TOCTOU(检查时间,使用时间)问题。我尝试使用在后台运行13个进程的Shell脚本创建相同的29元素路径来解决该bug,但没有成功。然后,我砍掉了测试程序,进行了20次分叉,并让每个孩子都尝试了一次,最终成功解决了该错误。固定代码将具有if (mkdir(path, mode) != 0 && errno != EEXIST) status = -1;。那没有显示错误。
乔纳森·勒夫勒

2
@DavidMerinos:他们是头(jlss.hemalloc.h),而不是库。但是,该代码在我的GitHub上的SOQ(堆栈溢出问题)存储库中以文件形式提供jlss.hemalloc.c并且emalloc.hsrc / libsoq子目录中可用。你需要posixver.h太,和其他几个人(debug.hstderr.cstderr.h-我认为这是它,但你需要什么都应该在该目录)。
乔纳森·莱夫勒

157

使用Boost.Filesystem轻松实现: create_directories

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

返回:true如果创建了新目录,则返回false


9
好吧,大多数boost库都是仅标头的,这意味着除了您使用的库之外没有任何开销。对于Boost.Filesystem,它需要进行编译。在我的磁盘上,编译后的库重约60KB。
贝诺瓦2009年

1
@Lipis:请精确说明您的嵌入式系统是什么。我相信几乎所有的Linux发行版都应该可以使用它。
贝诺瓦

4
关于对@danijar提到的C ++ 11个编译器,注释这里做更清楚: The <filesystem> header is not part of C++11; it is a proposal for C++ TR2 based on the Boost.Filesystem library. Visual C++ 2012 includes an implementation of the proposed library.
纯粮阖闾

5
升压::文件系统,不仅是报头:stackoverflow.com/questions/13604090/...
ftvs

2
恕我直言:在我的任何打算做一件有意义的事情并经得起时间考验的项目中,值得拥有这样一套令人难以置信的有用而强大的标准化工具作为推动工具是值得的。它中的很多已经进入标准C ++,当然还有更多。试一试,坚持下去,如果您的需求超出平凡,并且不想重新发明轮子,就会从中受益。:-)
moodboom

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

是我能想到的最短的方法(就代码的长度而言,不一定是执行时间)。

它不是跨平台的,但是可以在Linux下运行。


1
如果您打算将解决方案作为shell命令给出,那么最好提一下系统(3)
dmckee ---前主持人小猫

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

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

这里。您可能必须分别为/ tmp,/ tmp / a,/ tmp / a / b /和/ tmp / a / b / c分别设置mkdir,因为C api中没有-p标志。在进行上级操作时,请确保并忽略EEXISTS errno。


有趣的事实:至少Solaris和HP / UX具有mkdirp(),尽管显然对于可移植性而言并非最佳。
Martin Carpenter

关键是..我不想单独调用所有这些函数。
Lipis 2009年

多次调用mkdir会比一次调用系统更快。
Paul Tomblin,2009年

我不明白您的建议:用不同的参数调用mkdir 4次?("/tmp/",...)("/tmp/a/",...)("/tmp/a/b/",...)("/tmp/a/b/c/",...)
安东尼

1
再说一次,拨打相同的电话三次很简单。关键是要给人们足够的信息,使他们可以编写代码,而不是为他们编写代码。
Paul Tomblin

25

这是我的代码示例(适用于Windows和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;
}

用法:

$ makePath 1/2 folderA/folderB/folderC
creating 1/2 ... OK
creating folderA/folderB/folderC ... OK

我第二个评论!(令人惊讶地)找到一种可移植的C ++创建目录的方法并不是一件容易的事。这个答案需要更多的投票。
曼努埃尔·拉方

1
在Windows下,如果结尾字符为反斜杠,则isDirExist不起作用。始终返回false。我必须将代码修改为:std :: string dirPath(path); while('\\'== * dirPath.rbegin())dirPath.pop_back(); ...,然后,当然要在_stat的调用中传递dirPath.c_str()。
MiloDC '17

Windows API具有“与兼容性stat有关的非ANSI名称”,__STDC__无需进行预编译器测试。
桑德堡

18

应当注意,从C ++ 17文件系统接口开始是标准库的一部分。这意味着可以使用以下内容来创建目录:

#include <filesystem>

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

此处提供更多信息:https : //en.cppreference.com/w/cpp/filesystem/create_directory

另外,对于gcc,需要将“ -std = c ++ 17”转换为CFLAGS。和“ -lstdc ++ fs”到LDLIBS。将来可能不再需要后者。


还应该与足够新的Visual C ++和“ / std:c ++ latest”一起使用。请参阅:blogs.msdn.microsoft.com/vcblog/2018/05/07/…developercommunity.visualstudio.com/content/problem/296680/…–
罗恩·伯克

9

这与前面的类似,但是通过字符串前进而不是递归地前进。为errno保留上一次失败的正确值。如果有一个前导斜线,那么循环中会花费额外的时间,这可以通过在循环外使用一个find_first_of()或通过检测前导/并将pre设置为1来避免。循环或前循环调用,并且使用前循环调用时,复杂度会(稍高)。

#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';

}

7

您说的是“ C ++”,但是这里的每个人似乎都在思考“ Bash shell”。

查看gnu的源代码mkdir;然后您将看到如何在C ++中实现shell命令。


好system(“ mkdir ...”)应该在linux上就可以了。它不是跨平台的。
ChristopheD

我赞同@MartinCarpenter所说的话
Joshua Hedges

6
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;
}

这是对我最好的解决方案!谢谢!))))
Neo

也许这是一个转储问题,但是mkdir之前的“ ::”前缀是什么?
Rayee Roded '17

1
找到了答案,:: ::确保从全局名称空间stackoverflow.com/questions/4269034/…中
进行解析

4

所以我mkdirp()今天需要,并且发现此页面上的解决方案过于复杂。因此,我写了一个很短的代码段,很容易被其他偶然发现此线程的人复制,不知道为什么我们需要这么多代码行。

mkdirp

#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;
}

如果您不喜欢const强制转换并临时修改字符串,则在之后执行a strdup()free()它。


也张贴到要点上,所以我不会忘记下次需要它时将它放在哪里:) gist.github.com/jonasfj/7797272
jonasfj 2013年

2
尝试修改作为常量传递的字符串是邪恶的。除此以外,如果它传递字符串文字,很可能导致严重的失败。
Jonathan Leffler

2
完全正确...。这实际上很糟糕...可能strcpy会使它变得更好...
jonasfj15年

3

由于此帖子在“创建目录树”中在Google中排名很高,因此我将发布一个适用于Windows的答案-这将使用为UNICODE或MBCS编译的Win32 API起作用。这是从上面的Mark代码移植而来的。

由于我们使用的是Windows,因此目录分隔符为反斜杠,而不是正斜杠。如果您希望使用正斜杠,请更改'\\''/'

它将与:

c:\foo\bar\hello\world

c:\foo\bar\hellp\world\

(即:不需要尾部斜杠,因此您不必检查它。)

在说“仅在Windows中使用SHCreateDirectoryEx() ”之前,请注意,不建议使用SHCreateDirectoryEx(),并且可以随时从将来的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;
}

一个小的mod-如果路径包含反斜杠,则此命令不起作用。这里:`LPCTSTR szLast = _tcsrchr(szPathTree,'\\');`您只需要添加以下内容:```if(nullptr == szLast){szLast = _tcsrchr(szPathTree,'/'); }`
Den-Jason

1
谢谢(你的)信息。如果路径混合,会发生什么?即:c:\this\is\a/mixed/path\of\slashes通常Windows斜杠是反斜杠。应该发生的是,调用方应在调用此方法之前清理路径并确保所有斜杠均正确。
安迪

3

我知道这是一个古老的问题,但它在Google搜索结果中的排名很高,这里提供的答案并不是真正的C ++或过于复杂。

请注意,在我的示例中,createDirTree()非常简单,因为所有繁重的工作(错误检查,路径验证)仍然需要由createDir()完成。如果目录已经存在或整个项目无法正常工作,createDir()也应返回true。

这是我在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;
}

当然,createDir()函数将特定于系统,并且在其他答案中已经有足够的示例针对Linux编写该函数,因此我决定跳过它。


1

如果dir不存在,请创建它:

boost::filesystem::create_directories(boost::filesystem::path(output_file).parent_path().string().c_str()); 

1

这里描述了很多方法,但是其中大多数方法都需要对进入代码的路径进行硬编码。有一个简单的解决方案,使用QD框架的两类QDir和QFileInfo。由于您已经在Linux环境中,因此应该易于使用Qt。

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

确保您对该路径具有写权限。



0

这是C / C ++递归函数,可利用dirname()它遍历目录树的自底向上。一旦找到现有祖先,它将停止。

#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;
}

-2

其他人给了您正确的答案,但我认为我将向您展示您可以做的另一件整洁的事情:

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

将创建以下路径:

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

大括号允许您在层次结构的同一级别上一次创建多个目录,而该-p选项的意思是“根据需要创建父目录”。


看到保罗的回答后,我意识到我(以及很多其他人)误解了这个问题……
rmeador

如果有人可以通过更改为system(“ mkdir -p / tmp / a / {b,c} / d”)来更新它,那么问题就不在于在shell中进行此操作了,而是通过C ++实现的。
Lipis 2009年

我认为{a,b}在sh派生和csh派生的shell中都可以工作。我不确定它是否可以在system()命令中使用。
Paul Tomblin,2009年

1
@Lipis:通过system()进行操作不是解决OP问题的好方法。@Andy:我以前从未考虑过,但是我只是通过用“ echo”替换“ mkdir -p”来进行测试,并打印出“ / tmp / a / b / d / tmp / a / c / d”,这表明它是由外壳程序执行的,而不是mkdir。
rmeador

@rmeador:如果这不是一个好的解决方案,您还有其他建议吗?我想通过C ++来实现...这是我的问题,而不是如何通过外壳来实现
。.– Lipis
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.