我想要一种简单的方法来在C ++ / Linux中创建多个目录。
例如,我想在目录中保存文件lola.file:
/tmp/a/b/c
但是如果目录不存在,我希望它们可以自动创建。一个可行的例子将是完美的。
我想要一种简单的方法来在C ++ / Linux中创建多个目录。
例如,我想在目录中保存文件lola.file:
/tmp/a/b/c
但是如果目录不存在,我希望它们可以自动创建。一个可行的例子将是完美的。
Answers:
在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(答案的修正版)之间的变化是用于测试EEXIST
在
do_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
子目录中)获得。
if (errno != EEXIST) { status = -1; }
在mkdir失败时添加。
stat()
before 的麻烦mkdir()
;这是TOCTOU(检查时间,使用时间)问题。我尝试使用在后台运行13个进程的Shell脚本创建相同的29元素路径来解决该bug,但没有成功。然后,我砍掉了测试程序,进行了20次分叉,并让每个孩子都尝试了一次,最终成功解决了该错误。固定代码将具有if (mkdir(path, mode) != 0 && errno != EEXIST) status = -1;
。那没有显示错误。
jlss.h
,emalloc.h
),而不是库。但是,该代码在我的GitHub上的SOQ(堆栈溢出问题)存储库中以文件形式提供jlss.h
,emalloc.c
并且emalloc.h
在src / libsoq子目录中可用。你需要posixver.h
太,和其他几个人(debug.h
,stderr.c
,stderr.h
-我认为这是它,但你需要什么都应该在该目录)。
使用Boost.Filesystem轻松实现: create_directories
#include <boost/filesystem.hpp>
//...
boost::filesystem::create_directories("/tmp/a/b/c");
返回:true
如果创建了新目录,则返回false
。
system("mkdir -p /tmp/a/b/c")
是我能想到的最短的方法(就代码的长度而言,不一定是执行时间)。
它不是跨平台的,但是可以在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);
从这里。您可能必须分别为/ tmp,/ tmp / a,/ tmp / a / b /和/ tmp / a / b / c分别设置mkdir,因为C api中没有-p标志。在进行上级操作时,请确保并忽略EEXISTS errno。
("/tmp/",...)
,("/tmp/a/",...)
,("/tmp/a/b/",...)
,("/tmp/a/b/c/",...)
这是我的代码示例(适用于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
stat
有关的非ANSI名称”,__STDC__
无需进行预编译器测试。
应当注意,从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。将来可能不再需要后者。
这与前面的类似,但是通过字符串前进而不是递归地前进。为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';
}
您说的是“ C ++”,但是这里的每个人似乎都在思考“ Bash shell”。
查看gnu的源代码mkdir
;然后您将看到如何在C ++中实现shell命令。
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;
}
所以我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()
它。
由于此帖子在“创建目录树”中在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;
}
c:\this\is\a/mixed/path\of\slashes
通常Windows斜杠是反斜杠。应该发生的是,调用方应在调用此方法之前清理路径并确保所有斜杠均正确。
我知道这是一个古老的问题,但它在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编写该函数,因此我决定跳过它。
这里描述了很多方法,但是其中大多数方法都需要对进入代码的路径进行硬编码。有一个简单的解决方案,使用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());
}
确保您对该路径具有写权限。
这是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;
}
其他人给了您正确的答案,但我认为我将向您展示您可以做的另一件整洁的事情:
mkdir -p /tmp/a/{b,c}/d
将创建以下路径:
/tmp/a/b/d
/tmp/a/c/d
大括号允许您在层次结构的同一级别上一次创建多个目录,而该-p
选项的意思是“根据需要创建父目录”。