簡體   English   中英

如何在 C++/Linux 中創建目錄樹?

[英]How can I create directory tree in C++/Linux?

我想要一種在 C++/Linux 中創建多個目錄的簡單方法。

例如我想在目錄中保存一個文件 lola.file :

/tmp/a/b/c

但如果目錄不存在,我希望它們自動創建。 一個工作示例將是完美的。

使用 Boost.Filesystem 輕松實現: create_directories

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

返回: 如果創建了新目錄,則返回true ,否則返回false

With C++17 or later, there's the standard header <filesystem> with function std::filesystem::create_directories which should be used in modern C++ programs. 不過,C++ 標准函數沒有 POSIX 特定的顯式權限(模式)參數。

但是,這里有一個 C function 可以用 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" header 處理損壞的<sys/stat.h>版本,可以在現代 Unix 系統上用<sys/stat.h>替換(但早在 1990 年就有很多問題)。 並且"mkpath.h"聲明mkpath()

v1.12(答案的原始版本)和 v1.13(答案的修改版本)之間的變化是對do_mkdir()中的EEXIST的測試。 Switch指出這是必要的——謝謝你,Switch。 測試代碼已升級並在一台MacBook Pro(2.3GHz Intel Core i7,運行Mac OS X 10.7.4)上重現問題,並提示問題在revision中已修復(但測試只能顯示存在bug ,他們永遠不會缺席)。 顯示的代碼現在是 v1.16; 自 v1.13 以來已經進行了外觀或管理更改(例如使用mkpath.h代替jlss.h並且僅在測試代碼中無條件地包含<unistd.h> )。 有理由認為"sysstat.h"應該被替換為<sys/stat.h>除非你有一個異常頑固的系統。

(特此允許您出於任何目的使用此代碼並注明出處。)

此代碼可在我的 GitHub 上的SOQ (堆棧溢出問題)存儲庫中作為文件mkpath.cmkpath.h (等)在src/so-0067-5039子目錄中找到。

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

是我能想到的最短方式(就代碼長度而言,不一定是執行時間)。

它不是跨平台的,但可以在 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
#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。

需要注意的是,從 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 來避免。無論我們是由 a 設置的,效率都是相同的第一個循環或預循環調用,使用預循環調用時復雜性會(略)高一些。

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

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

您說的是“C++”,但這里的每個人似乎都在想“Bash shell”。

查看 gnu mkdir的源代碼; 然后你可以看到如何在 C++ 中實現 shell 命令。

所以我今天需要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 強制轉換和臨時修改字符串,只需在之后執行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++ 中,或者有點太復雜了。

請注意,在我的示例中 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() function 將是系統特定的,並且在其他答案中已經有足夠的示例如何為 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());
}

確保您對該路徑具有寫入權限。

這是 C/C++ 遞歸 function 使用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 並尋找與平台無關的解決方案,請使用ghc::filesystem header-ony 代碼與 C++17(實際上是一個反向端口)兼容,並且以后易於遷移。

mkdir -p /dir/to/the/file

touch /dir/to/the/file/thefile.ending

其他人給了你正確的答案,但我想我會展示你可以做的另一件巧妙的事情:

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

將創建以下路徑:

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

大括號允許您在層次結構的同一級別上一次創建多個目錄,而-p選項表示“根據需要創建父目錄”。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM