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

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

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

/tmp/a/b/c

但是如果目录不在那里,我希望能自动创建它们。

241039 次浏览
system("mkdir -p /tmp/a/b/c")

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

它不是跨平台的,但是可以在 Linux 下工作。

mkdir -p /dir/to/the/file


touch /dir/to/the/file/thefile.ending
#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 分别执行 mkdirs,因为在 C api 中没有等效的-p 标志。请确保并忽略 EEXISTS errno,而您正在执行较高级别的操作。

其他人给了你正确的答案,但是我想我可以展示另一个你可以做的很棒的事情:

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

将创建以下路径:

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

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

你说的是“ C + +”但这里的每个人似乎都在想“ Bash shell”

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

文件系统: create_directories

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

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

对于 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.cestrdup.c)。 "sysstat.h"报头处理 <sys/stat.h>的破碎版本 并且在现代 Unix 系统上可以被 <sys/stat.h>所取代(但是 是1990年的许多问题)。 "mkpath.h"声明 mkpath()

1.12版本(答案的原始版本)和1.13版本之间的变化 (修订版本的答案)是测试的 EEXISTdo_mkdir(). 这一点是由 谢谢 你,Switch。 测试代码已经升级,并在 MacBook 上重现了问题 Pro (2.3 GHz Intel Core i7,运行 Mac OS X 10.7.4) ,并建议 这个问题在修订版中已经解决了(但是测试只能显示 错误的存在,从来没有他们的缺席)。 所显示的代码现在是 v1.16; 已经有了化妆品或管理 自1.13版以来所做的更改(例如使用 mkpath.h而不是 jlss.h和 只在测试代码中无条件包含 <unistd.h>)。 有理由认为 "sysstat.h"应该被 除非你有一个异常顽固的系统。

(特此授权您使用本代码进行任何具有署名的用途。)

这段代码可以在我的 < a href = “ https://github.com/jleffler/SOQ”rel = “ norefrer”> SOQ 中找到 (堆栈溢出问题)在 GitHub 上作为文件 mkpath.cmkpath.h(等) Src/so-0067-5039 子目录。

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

这与前一个类似,但是通过字符串向前工作,而不是递归地向后工作。将 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';


}

因为这篇文章在谷歌的“创建目录树”排名很高,所以我打算发布一个适用于 Windows 的答案ーー这个答案将使用为 UnicODE 或 MBCS 编译的 Win32 API。这是从上面马克的代码移植过来的。

因为这是我们使用的 Windows,所以目录分隔符是 back 斜杠,而不是 forward 斜杠。如果您想使用正斜杠,请将 '\\'更改为 '/'

它将与:

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

所以我需要 mkdirp()今天,并发现这一页上的解决方案过于复杂。 因此,我编写了一个相当简短的代码片段,可以很容易地复制给其他人 偶然发现了这个线程,奇怪为什么我们需要这么多行代码。

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

如果您不喜欢常量强制转换并临时修改字符串,那么只需在之后执行 strdup()free()操作。

我知道这是一个老问题,但它显示在谷歌搜索结果高,这里提供的答案不是真正的 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 编写它,所以我决定跳过它。

下面是我的代码示例(它同时适用于 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

如果 dir 不存在,创建它:

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

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

#include <filesystem>


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

更多信息请点击: https://en.cppreference.com/w/cpp/filesystem/create_directory

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

这里已经描述了很多方法,但是大多数方法都需要将您的路径硬编码到代码中。 对于这个问题有一个简单的解决方案,使用 QDir 和 QFileInfo 这两类 Qt 框架。因为您已经在 Linux 环境中,所以应该很容易使用 Qt。

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

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

下面是 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;
}

如果您还没有 C + + 17,并且正在寻找与平台无关的解决方案,那么使用 : 文件系统。Header-ony 代码与 C + + 17兼容(实际上是一个 backport) ,并且很容易在以后进行迁移。