ฉันจะสร้างไดเร็กทอรีทรีใน C ++ / Linux ได้อย่างไร


109

ฉันต้องการวิธีง่ายๆในการสร้างไดเรกทอรีหลายรายการใน C ++ / Linux

ตัวอย่างเช่นฉันต้องการบันทึกไฟล์ lola.file ในไดเร็กทอรี:

/tmp/a/b/c

แต่ถ้าไม่มีไดเรกทอรีฉันต้องการให้สร้างโดยอัตโนมัติ ตัวอย่างการทำงานจะสมบูรณ์แบบ


C ++ ไม่ได้มีในตัวสิ่งอำนวยความสะดวกสำหรับการสร้างไดเรกทอรีและต้นไม้ต่อ se คุณจะต้องใช้ C และการโทรระบบหรือไลบรารีภายนอกเช่น Boost C และการเรียกระบบจะขึ้นอยู่กับแพลตฟอร์ม
jww

6
@noloader ขอบคุณหลาย ๆ คน .. แต่ฉันคิดว่าหลังจาก 4 ปีฉันได้รับคำตอบของฉันค่อนข้างมากเพราะคุณสามารถเห็นการร้องใน 13 วิธีที่แตกต่างกัน ...
Lipis

ใช่ฉันรู้สึกประหลาดใจที่ไม่มีใครระบุอย่างชัดเจนว่าคุณไม่สามารถทำได้ใน C ++ (สมมติว่าคุณต้องการวิธีพกพาใน C ++ ที่ทำงานบน Linux) แต่คุณคงรู้แล้ว;). มีคำแนะนำที่ดีมากมายสำหรับรหัส C ที่ไม่พกพาได้
jww

"C ++ / Linux" คืออะไร?
Lightness Races ใน Orbit

3
@LightnessRacesinOrbit เป็นปีมหาวิทยาลัยของฉันใน C ++ บน Linux :)
Lipis

คำตอบ:


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>บนระบบปฏิบัติการยูนิกซ์ที่ทันสมัย ( แต่มีปัญหาหลายอย่างกลับไปในปี 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


2
มันเร็วกว่าระบบแน่นอน ระบบมีค่าใช้จ่ายจำนวนมากที่เกี่ยวข้อง โดยทั่วไปจะต้องมีการแยกกระบวนการจากนั้นต้องโหลดไบนารีอย่างน้อยสองรายการ (อันหนึ่งอาจอยู่ในแคชแล้ว) ซึ่งจะเป็นทางแยกของอีกอันหนึ่ง ...
ypnos

1
ฉันลืม: จากนั้น "mkdir -p" จะทำอย่างน้อยเหมือนกับรหัสที่โพสต์ไว้ด้านบน!
ypnos

7
มีเงื่อนไขการแข่งขันที่ละเอียดอ่อนในรหัสนี้ที่ฉันตีจริง จะเกิดขึ้นเฉพาะเมื่อหลายโปรแกรมเริ่มต้นพร้อมกันและสร้างเส้นทางโฟลเดอร์เดียวกัน การแก้ไขคือการเพิ่มif (errno != EEXIST) { status = -1; }เมื่อ mkdir ล้มเหลว
เปลี่ยน

2
@Switch: ขอบคุณ นั่นเป็นปัญหากับการใช้stat()ก่อนmkdir(); เป็นปัญหา TOCTOU (เวลาตรวจสอบเวลาใช้งาน) ฉันพยายามกระตุ้นข้อบกพร่องด้วยเชลล์สคริปต์ที่เรียกใช้กระบวนการ 13 ในพื้นหลังโดยสร้างเส้นทาง 29 องค์ประกอบเดียวกันและไม่สามารถจัดการได้ จากนั้นฉันก็แฮ็คโปรแกรมทดสอบเพื่อแยก 20 ครั้งและให้เด็กแต่ละคนลองและนั่นก็สามารถจัดการกับข้อบกพร่องได้ if (mkdir(path, mode) != 0 && errno != EEXIST) status = -1;รหัสคงที่จะมี นั่นไม่ได้แสดงจุดบกพร่อง
Jonathan Leffler

2
@DavidMerinos: เป็นส่วนหัว ( jlss.h, emalloc.h) ไม่ใช่ไลบรารี อย่างไรก็ตามรหัสที่มีอยู่ในของฉันSOQ (คำถามที่กองมากเกิน) พื้นที่เก็บข้อมูลบน GitHub เป็นไฟล์jlss.h, emalloc.cและemalloc.hในsrc / libsoqไดเรกทอรีย่อย คุณจะต้องposixver.hเกินไปและอีกสองสามคน ( debug.h, stderr.c, stderr.h- ผมคิดว่ามัน แต่สิ่งที่คุณต้องการทุกคนควรจะอยู่ในไดเรกทอรีที่)
Jonathan Leffler

157

ง่ายด้วย Boost ระบบไฟล์: create_directories

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

ผลตอบแทน: ถ้าไดเรกทอรีใหม่ถูกสร้างขึ้นมิฉะนั้นtruefalse


9
ไลบรารีบูสต์ส่วนใหญ่เป็นส่วนหัวเท่านั้นหมายความว่าไม่มีค่าใช้จ่ายใด ๆ นอกเหนือจากสิ่งที่คุณใช้ ในกรณีของ Boost.Filesystem จำเป็นต้องมีการคอมไพล์ บนดิสก์ของฉันไลบรารีที่คอมไพล์แล้วมีน้ำหนัก ~ 60KB
Benoît

1
@ Lipis: โปรดระบุว่าระบบฝังตัวของคุณคืออะไร ฉันเชื่อว่ามันควรจะมีอยู่ในลินุกซ์ทุกตัว
Benoît

4
เกี่ยวกับคอมไพเลอร์ C ++ 11 ที่กล่าวถึงโดย @danijar ความคิดเห็นที่นี่ทำให้ชัดเจนขึ้น: 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.
Chunliang Lyu

5
boost :: filesystem ไม่ใช่ส่วนหัวเท่านั้น: stackoverflow.com/questions/13604090/…
ftvs

2
IMHO: ในโครงการใด ๆ ของฉันที่ตั้งใจจะทำบางสิ่งที่สำคัญและยืนหยัดการทดสอบของเวลามันคุ้มค่าที่จะรวบรวมชุดเครื่องมือมาตรฐานที่มีประโยชน์และทรงพลังอย่างเหลือเชื่อเช่นการเพิ่ม จำนวนมากได้เข้าสู่ C ++ มาตรฐานแล้วและมีอะไรอีกมากมายที่จะตามมา ลองใช้ดูสิคุณจะได้รับประโยชน์หากคุณมีมากกว่าความต้องการเล็กน้อยและคุณไม่ต้องการสร้างล้อใหม่ :-)
moodboom

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

เป็นวิธีที่สั้นที่สุดที่ฉันคิดได้ (ในแง่ของความยาวของโค้ดไม่จำเป็นต้องเป็นเวลาดำเนินการ)

ไม่ใช่ข้ามแพลตฟอร์ม แต่จะทำงานภายใต้ Linux


1
หากคุณจะให้โซลูชันเป็นคำสั่งเชลล์ก็ควรจะกล่าวถึงระบบ (3)
dmckee --- ex-moderator kitten

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

จากที่นี่ . คุณอาจต้องแยก mkdirs สำหรับ / tmp, / tmp / a, / tmp / a / b / แล้ว / tmp / a / b / c เนื่องจากไม่มีแฟล็ก -p ที่เทียบเท่าใน C api ตรวจสอบให้แน่ใจและละเว้นข้อผิดพลาด EEXISTS ในขณะที่คุณกำลังดำเนินการระดับสูง


ข้อเท็จจริงที่น่าสนใจ: อย่างน้อย Solaris และ HP / UX ก็มี mkdirp () แม้ว่าจะไม่เหมาะสมสำหรับการพกพาก็ตาม
Martin Carpenter

นั่นคือประเด็น .. ที่ฉันไม่ต้องการเรียกฟังก์ชันทั้งหมดนี้แยกจากกัน
Lipis

การโทรหา mkdir ไม่กี่ครั้งจะเร็วกว่าระบบโทรครั้งเดียว
Paul Tomblin

ฉันไม่เข้าใจสิ่งที่คุณแนะนำ: โทรหา 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 ++ แบบพกพาในการสร้างไดเร็กทอรี คำตอบนี้ต้องการการโหวตเพิ่มขึ้น
Manuel Lafond

1
ภายใต้ Windows isDirExist จะไม่ทำงานหากอักขระต่อท้ายเป็นแบ็กสแลช ส่งคืนเท็จเสมอ ฉันต้องแก้ไขรหัสเป็น: std :: string dirPath (path); ในขณะที่ ('\\' == * dirPath.rbegin ()) dirPath.pop_back (); ... จากนั้นส่ง dirPath.c_str () ในการเรียกไปที่ _stat
MiloDC

Windows API มี "ชื่อที่ไม่ใช่ ANSI สำหรับความเข้ากันได้" สำหรับstat(ที่เกี่ยวข้องกับ__STDC__) ไม่จำเป็นต้องทดสอบพรีคอมไพเลอร์
Sandburg

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 ++ ล่าสุด" ที่ใหม่เพียงพอ ดู: blogs.msdn.microsoft.com/vcblog/2018/05/07/… และdevelopercommunity.visualstudio.com/content/problem/296680/…
รอน

9

สิ่งนี้คล้ายกับก่อนหน้านี้ แต่ทำงานไปข้างหน้าผ่านสตริงแทนที่จะย้อนกลับแบบวนซ้ำ ทิ้งข้อผิดพลาดด้วยค่าที่เหมาะสมสำหรับความล้มเหลวครั้งล่าสุด หากมีเครื่องหมายทับนำหน้าจะมีเวลาพิเศษในการวนซ้ำซึ่งสามารถหลีกเลี่ยงได้ผ่าน 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';

}

7

คุณพูดว่า "C ++" แต่ทุกคนที่นี่ดูเหมือนจะคิดว่า "Bash shell"

ตรวจสอบซอร์สโค้ดเป็น gnu mkdir; จากนั้นคุณสามารถดูวิธีใช้คำสั่งเชลล์ใน C ++


ระบบดี ("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;
}

เป็นทางออกที่ดีที่สุดสำหรับฉัน! ขอบคุณ!)))
นีโอ

อาจเป็นคำถามเกี่ยวกับการถ่ายโอนข้อมูล แต่คำนำหน้า "::" ก่อน mkdir คืออะไร
Rayee Roded

1
พบคำตอบ :: ทำให้มั่นใจได้ว่าความละเอียดเกิดขึ้นจาก global namespace stackoverflow.com/questions/4269034/…
Rayee Roded

4

ดังนั้นฉันต้องการ 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()หลังจากนั้น


โพสต์ไปยังส่วนสำคัญด้วยดังนั้นฉันจะไม่ลืมว่าฉันจะวางไว้ที่ไหนในครั้งต่อไปที่จำเป็น :) gist.github.com/jonasfj/7797272
jonasfj

2
เป็นการดีที่จะพยายามแก้ไขสตริงที่ส่งผ่านเป็นค่าคงที่ นอกจากนี้ยังมีแนวโน้มที่จะนำไปสู่ความล้มเหลวอย่างมากหากมีการส่งผ่านสตริงตามตัวอักษร
Jonathan Leffler

2
สมบูรณ์ถูกต้อง .... แย่จริง ... อาจจะ strcpy จะทำให้ดีกว่านี้ ...
jonasfj

3

เนื่องจากโพสต์นี้อยู่ในอันดับที่สูงใน 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;
}

ตัวดัดแปลงเล็กน้อย - หากเส้นทางมีเครื่องหมายแบ็กสแลชจะไม่ได้ผล ที่นี่: `LPCTSTR szLast = _tcsrchr (szPathTree, '\\'); คุณแค่ต้องเพิ่มสิ่งนี้:` `if (nullptr == szLast) {szLast = _tcsrchr (szPathTree, '/'); } ``
Den-Jason

1
ขอบคุณสำหรับข้อมูล. จะเกิดอะไรขึ้นถ้าเส้นทางผสมกัน? เช่น: c:\this\is\a/mixed/path\of\slashesโดยทั่วไปแล้วเครื่องหมายทับของ Windows จะเป็นแบ็กสแลช สิ่งที่ควรเกิดขึ้นคือผู้โทรควรทำความสะอาดเส้นทางและตรวจสอบให้แน่ใจว่าเครื่องหมายทับทั้งหมดถูกต้องก่อนที่จะเรียกใช้วิธีนี้
Andy

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

มีการอธิบายวิธีการมากมายไว้ที่นี่ แต่ส่วนใหญ่ต้องการการเข้ารหัสเส้นทางของคุณในโค้ดของคุณอย่างหนัก มีวิธีง่ายๆสำหรับปัญหานั้นโดยใช้ 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());
}

ตรวจสอบให้แน่ใจว่าคุณมีสิทธิ์ในการเขียนเส้นทางนั้น



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ตัวเลือกหมายถึง "สร้างไดเร็กทอรีพาเรนต์ตามต้องการ"


หลังจากเห็นคำตอบของ Paul ฉันก็รู้ว่าฉัน (และคนอื่น ๆ อีกมากมาย) เข้าใจคำถามนี้ผิด ...
rmeador

หากมีใครสามารถอัปเดตสิ่งนี้ได้โดยเปลี่ยนเป็นระบบ ("mkdir -p / tmp / a / {b, c} / d") สาเหตุที่คำถามไม่ได้เกี่ยวกับการทำในเชลล์ .. แต่ใช้ C ++
Lipis

ฉันคิดว่า {a, b} จะใช้ได้ทั้งในเชลล์ที่ได้รับจาก sh และที่ได้รับ csh ฉันไม่แน่ใจว่ามันจะทำงานในคำสั่ง system () ได้หรือไม่
Paul Tomblin

1
@ Lipis: การทำเช่นนั้นผ่านระบบ () ไม่ใช่วิธีแก้ปัญหาที่ดีสำหรับคำถามของ OP @ แอนดี้: ฉันไม่เคยคิดแบบนั้นมาก่อน แต่ฉันเพิ่งทดสอบโดยแทนที่ "mkdir -p" ด้วย "echo" และมันจะพิมพ์ออกมา "/ tmp / a / b / d / tmp / a / c / d", ซึ่งชี้ให้เห็นว่ามันเป็นเปลือกที่ทำมันไม่ใช่ mkdir
rmeador

@rmeador: ถ้ามันไม่ใช่ทางออกที่ดีคุณมีอะไรจะแนะนำอีกไหม? ฉันต้องการทำผ่าน C ++ ... นั่นคือปัญหาของฉันไม่ใช่ว่าจะทำผ่านเชลล์ได้อย่างไร ..
Lipis
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.