ฉันต้องการวิธีง่ายๆในการสร้างไดเรกทอรีหลายรายการใน C ++ / Linux
ตัวอย่างเช่นฉันต้องการบันทึกไฟล์ lola.file ในไดเร็กทอรี:
/tmp/a/b/c
แต่ถ้าไม่มีไดเรกทอรีฉันต้องการให้สร้างโดยอัตโนมัติ ตัวอย่างการทำงานจะสมบูรณ์แบบ
ฉันต้องการวิธีง่ายๆในการสร้างไดเรกทอรีหลายรายการใน C ++ / Linux
ตัวอย่างเช่นฉันต้องการบันทึกไฟล์ lola.file ในไดเร็กทอรี:
/tmp/a/b/c
แต่ถ้าไม่มีไดเรกทอรีฉันต้องการให้สร้างโดยอัตโนมัติ ตัวอย่างการทำงานจะสมบูรณ์แบบ
คำตอบ:
ด้วย 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>
บนระบบปฏิบัติการยูนิกซ์ที่ทันสมัย ( แต่มีปัญหาหลายอย่างกลับไปในปี 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>
เว้นแต่คุณจะมีระบบบิดพลิ้วผิดปกติ
(คุณได้รับอนุญาตให้ใช้รหัสนี้เพื่อวัตถุประสงค์ใด ๆ กับการแสดงที่มา)
รหัสนี้มีอยู่ในที่เก็บ SOQ
(Stack Overflow Questions) ของฉันบน GitHub เป็นไฟล์mkpath.c
และ
mkpath.h
(ฯลฯ ) ใน
ไดเร็กทอรี
ย่อยsrc / so-0067-5039
if (errno != EEXIST) { status = -1; }
เมื่อ mkdir ล้มเหลว
stat()
ก่อนmkdir()
; เป็นปัญหา TOCTOU (เวลาตรวจสอบเวลาใช้งาน) ฉันพยายามกระตุ้นข้อบกพร่องด้วยเชลล์สคริปต์ที่เรียกใช้กระบวนการ 13 ในพื้นหลังโดยสร้างเส้นทาง 29 องค์ประกอบเดียวกันและไม่สามารถจัดการได้ จากนั้นฉันก็แฮ็คโปรแกรมทดสอบเพื่อแยก 20 ครั้งและให้เด็กแต่ละคนลองและนั่นก็สามารถจัดการกับข้อบกพร่องได้ if (mkdir(path, mode) != 0 && errno != EEXIST) status = -1;
รหัสคงที่จะมี นั่นไม่ได้แสดงจุดบกพร่อง
jlss.h
, emalloc.h
) ไม่ใช่ไลบรารี อย่างไรก็ตามรหัสที่มีอยู่ในของฉันSOQ (คำถามที่กองมากเกิน) พื้นที่เก็บข้อมูลบน GitHub เป็นไฟล์jlss.h
, emalloc.c
และemalloc.h
ในsrc / libsoqไดเรกทอรีย่อย คุณจะต้องposixver.h
เกินไปและอีกสองสามคน ( debug.h
, stderr.c
, stderr.h
- ผมคิดว่ามัน แต่สิ่งที่คุณต้องการทุกคนควรจะอยู่ในไดเรกทอรีที่)
ง่ายด้วย Boost ระบบไฟล์: create_directories
#include <boost/filesystem.hpp>
//...
boost::filesystem::create_directories("/tmp/a/b/c");
ผลตอบแทน: ถ้าไดเรกทอรีใหม่ถูกสร้างขึ้นมิฉะนั้นtrue
false
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.
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);
จากที่นี่ . คุณอาจต้องแยก mkdirs สำหรับ / tmp, / tmp / a, / tmp / a / b / แล้ว / tmp / a / b / c เนื่องจากไม่มีแฟล็ก -p ที่เทียบเท่าใน C api ตรวจสอบให้แน่ใจและละเว้นข้อผิดพลาด EEXISTS ในขณะที่คุณกำลังดำเนินการระดับสูง
("/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
(ที่เกี่ยวข้องกับ__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 สิ่งหลังนี้อาจไม่จำเป็นในอนาคต
สิ่งนี้คล้ายกับก่อนหน้านี้ แต่ทำงานไปข้างหน้าผ่านสตริงแทนที่จะย้อนกลับแบบวนซ้ำ ทิ้งข้อผิดพลาดด้วยค่าที่เหมาะสมสำหรับความล้มเหลวครั้งล่าสุด หากมีเครื่องหมายทับนำหน้าจะมีเวลาพิเศษในการวนซ้ำซึ่งสามารถหลีกเลี่ยงได้ผ่าน find_first_of () หนึ่งอันนอกลูปหรือโดยการตรวจจับการนำหน้า / และการตั้งค่าก่อนถึง 1 ประสิทธิภาพจะเท่ากันไม่ว่าเราจะตั้งค่าโดย a ลูปแรกหรือการโทรแบบ pre-loop และความซับซ้อนจะสูงขึ้น (เล็กน้อย) เมื่อใช้การโทรแบบ pre-loop
#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 ++
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.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;
}
หากคุณไม่ชอบการแคสต์ const และแก้ไขสตริงชั่วคราวให้ทำ a strdup()
และfree()
หลังจากนั้น
เนื่องจากโพสต์นี้อยู่ในอันดับที่สูงใน Google สำหรับ "Create Directory Tree" ฉันจะโพสต์คำตอบที่ใช้ได้กับ Windows ซึ่งจะใช้งานได้โดยใช้ Win32 API ที่คอมไพล์สำหรับ UNICODE หรือ MBCS พอร์ตนี้มาจากรหัสของ Mark ด้านบน
เนื่องจากนี่คือ Windows ที่เรากำลังทำงานอยู่ตัวคั่นไดเรกทอรีจึงเป็นเครื่องหมายทับกลับไม่ใช่เครื่องหมายทับ หากคุณต้องการใช้เครื่องหมายทับให้เปลี่ยน'\\'
เป็น'/'
มันจะทำงานร่วมกับ:
c:\foo\bar\hello\world
และ
c:\foo\bar\hellp\world\
(เช่น: ไม่จำเป็นต้องมีเครื่องหมายทับดังนั้นคุณไม่จำเป็นต้องตรวจสอบ)
ก่อนที่จะพูดว่า "Just use SHCreateDirectoryEx ()ใน Windows" โปรดทราบว่า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 ดังนั้นฉันจึงตัดสินใจข้ามไป
หากไม่มี dir ให้สร้าง:
boost::filesystem::create_directories(boost::filesystem::path(output_file).parent_path().string().c_str());
มีการอธิบายวิธีการมากมายไว้ที่นี่ แต่ส่วนใหญ่ต้องการการเข้ารหัสเส้นทางของคุณในโค้ดของคุณอย่างหนัก มีวิธีง่ายๆสำหรับปัญหานั้นโดยใช้ 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());
}
ตรวจสอบให้แน่ใจว่าคุณมีสิทธิ์ในการเขียนเส้นทางนั้น
mkdir -p /dir/to/the/file
touch /dir/to/the/file/thefile.ending
-p
ตัวเลือกคือสิ่งที่ผมกำลังมองหา ขอบคุณ!
นี่คือฟังก์ชันเรียกซ้ำ 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
ตัวเลือกหมายถึง "สร้างไดเร็กทอรีพาเรนต์ตามต้องการ"