Tôi muốn một cách dễ dàng để tạo nhiều thư mục trong C ++ / Linux.
Ví dụ, tôi muốn lưu một tệp lola.file trong thư mục:
/tmp/a/b/c
nhưng nếu các thư mục không có ở đó, tôi muốn chúng được tạo tự động. Một ví dụ làm việc sẽ là hoàn hảo.
Tôi muốn một cách dễ dàng để tạo nhiều thư mục trong C ++ / Linux.
Ví dụ, tôi muốn lưu một tệp lola.file trong thư mục:
/tmp/a/b/c
nhưng nếu các thư mục không có ở đó, tôi muốn chúng được tạo tự động. Một ví dụ làm việc sẽ là hoàn hảo.
Câu trả lời:
Với C ++ 17 trở lên, có tiêu đề tiêu chuẩn <filesystem>
với hàm
std::filesystem::create_directories
nên được sử dụng trong các chương trình C ++ hiện đại. Mặc dù vậy, các hàm tiêu chuẩn C ++ không có đối số quyền (chế độ) rõ ràng dành riêng cho POSIX.
Tuy nhiên, đây là một hàm C có thể được biên dịch bằng trình biên dịch 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 */
Các macro STRDUP()
và FREE()
là phiên bản kiểm tra lỗi của
strdup()
và free()
, được khai báo trong emalloc.h
(và được triển khai trong
emalloc.c
và estrdup.c
). Các "sysstat.h"
giao dịch tiêu đề với các phiên bản chia của <sys/stat.h>
và có thể được thay thế bằng <sys/stat.h>
trên các hệ thống Unix hiện đại (nhưng có rất nhiều vấn đề trở lại vào năm 1990). Và "mkpath.h"
tuyên bố mkpath()
.
Sự thay đổi giữa v1.12 (phiên bản gốc của câu trả lời) và v1.13 (phiên bản sửa đổi của câu trả lời) là thử nghiệm cho EEXIST
trong
do_mkdir()
. Điều này đã được Switch chỉ ra là cần thiết
- cảm ơn bạn, Switch. Mã kiểm tra đã được nâng cấp và tái tạo sự cố trên MacBook Pro (Intel Core i7 2.3GHz, chạy Mac OS X 10.7.4) và cho thấy rằng sự cố đã được khắc phục trong bản sửa đổi (nhưng thử nghiệm chỉ có thể cho thấy sự hiện diện của lỗi , không bao giờ vắng mặt của họ). Mã hiển thị bây giờ là v1.16; đã có những thay đổi về mỹ phẩm hoặc hành chính được thực hiện kể từ v1.13 (chẳng hạn như sử dụng mkpath.h
thay vì jlss.h
và chỉ đưa <unistd.h>
vào mã thử nghiệm một cách vô điều kiện). Thật hợp lý khi lập luận rằng "sysstat.h"
nên được thay thế bằng
<sys/stat.h>
trừ khi bạn có một hệ thống ngoan cố bất thường.
(Theo đây, bạn được phép sử dụng mã này cho bất kỳ mục đích nào có ghi công.)
Mã này có sẵn trong
kho lưu trữ SOQ (Câu hỏi dồn về ngăn xếp) của tôi trên GitHub dưới dạng tệp mkpath.c
và
mkpath.h
(v.v.) trong thư mục con
src / so-0067-5039
.
if (errno != EEXIST) { status = -1; }
khi mkdir bị lỗi.
stat()
trước đây mkdir()
; nó là một vấn đề TOCTOU (thời gian kiểm tra, thời gian sử dụng). Tôi đã thử kiểm tra lỗi bằng một tập lệnh shell chạy 13 quy trình trong nền tạo ra cùng một đường dẫn 29 phần tử và không tìm cách khắc phục nó. Sau đó, tôi đã hack chương trình thử nghiệm đến 20 lần và yêu cầu mỗi đứa trẻ thử, và điều đó đã giải quyết được lỗi. Mã cố định sẽ có if (mkdir(path, mode) != 0 && errno != EEXIST) status = -1;
. Điều đó không hiển thị lỗi.
jlss.h
, emalloc.h
), không phải thư viện. Tuy nhiên, mã có sẵn trong tôi SOQ (Câu hỏi Stack Overflow) kho trên GitHub như các file jlss.h
, emalloc.c
và emalloc.h
trong src / libsoq thư mục con. Bạn sẽ cần phải posixver.h
quá, và một vài người khác ( debug.h
, stderr.c
, stderr.h
- Tôi nghĩ đó là nó, nhưng những gì bạn cần tất cả phải ở trong thư mục đó).
Dễ dàng với Boost. create_directories
#include <boost/filesystem.hpp>
//...
boost::filesystem::create_directories("/tmp/a/b/c");
Trả về: true
nếu một thư mục mới được tạo, ngược lại 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")
là cách ngắn nhất mà tôi có thể nghĩ ra (về độ dài của mã, không nhất thiết là thời gian thực thi).
Nó không đa nền tảng nhưng sẽ hoạt động trên 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);
Từ đây . Bạn có thể phải thực hiện các mkdirs riêng biệt cho / tmp, / tmp / a, / tmp / a / b / và sau đó là / tmp / a / b / c vì không có cờ -p tương đương trong C api. Hãy chắc chắn và bỏ qua lỗi EEXISTS trong khi bạn đang thực hiện các bước cấp cao hơn.
("/tmp/",...)
, ("/tmp/a/",...)
, ("/tmp/a/b/",...)
,("/tmp/a/b/c/",...)
Đây là ví dụ của tôi về mã (nó hoạt động cho cả Windows và 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;
}
Sử dụng:
$ makePath 1/2 folderA/folderB/folderC
creating 1/2 ... OK
creating folderA/folderB/folderC ... OK
stat
(liên quan đến __STDC__
) không cần kiểm tra trình biên dịch trước.
Cần lưu ý rằng bắt đầu từ C ++ 17 giao diện hệ thống tệp là một phần của thư viện chuẩn. Điều này có nghĩa là người ta có thể có những điều sau để tạo thư mục:
#include <filesystem>
std::filesystem::create_directories("/a/b/c/d")
Thông tin thêm tại đây: https://en.cppreference.com/w/cpp/filesystem/create_directory
Ngoài ra, với gcc, người ta cần "-std = c ++ 17" thành CFLAGS. Và "-lstdc ++ fs" thành LDLIBS. Khả năng sau này sẽ không được yêu cầu trong tương lai.
Điều này tương tự như trước đó nhưng hoạt động chuyển tiếp thông qua chuỗi thay vì ngược lại một cách đệ quy. Để lại sai sót với giá trị phù hợp cho lần thất bại cuối cùng. Nếu có dấu gạch chéo ở đầu, sẽ có thêm thời gian thông qua vòng lặp mà có thể tránh được thông qua một find_first_of () bên ngoài vòng lặp hoặc bằng cách phát hiện dấu gạch chéo đầu / và đặt trước 1. Hiệu quả là như nhau cho dù chúng ta có thiết lập vòng lặp đầu tiên hoặc một cuộc gọi vòng lặp trước và độ phức tạp sẽ cao hơn (một chút) khi sử dụng cuộc gọi vòng lặp trước.
#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';
}
Bạn đã nói "C ++" nhưng mọi người ở đây dường như đang nghĩ "Bash shell."
Kiểm tra mã nguồn để gnu mkdir
; thì bạn có thể xem cách triển khai các lệnh shell trong 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;
}
Vì vậy, tôi cần mkdirp()
ngày hôm nay và nhận thấy các giải pháp trên trang này quá phức tạp. Do đó, tôi đã viết một đoạn mã khá ngắn, có thể dễ dàng sao chép vào cho những người khác tình cờ gặp chủ đề này và tự hỏi tại sao chúng ta cần nhiều dòng mã như vậy.
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;
}
Nếu bạn không thích ép kiểu const và tạm thời sửa đổi chuỗi, chỉ cần thực hiện một strdup()
và free()
sau đó.
Vì bài đăng này được xếp hạng cao trong Google cho "Tạo cây thư mục", tôi sẽ đăng một câu trả lời sẽ hoạt động cho Windows - điều này sẽ hoạt động bằng cách sử dụng Win32 API được biên dịch cho UNICODE hoặc MBCS. Điều này được chuyển từ mã của Mark ở trên.
Vì đây là Windows mà chúng tôi đang làm việc, các dấu phân cách thư mục là dấu gạch chéo LẠI, không phải dấu gạch chéo về phía trước. Nếu bạn muốn có dấu gạch chéo về phía trước, hãy thay đổi '\\'
thành'/'
Nó sẽ hoạt động với:
c:\foo\bar\hello\world
và
c:\foo\bar\hellp\world\
(nghĩa là: không cần dấu gạch chéo, vì vậy bạn không cần phải kiểm tra nó.)
Trước khi nói "Chỉ sử dụng SHCreateDirectoryEx () trong Windows", hãy lưu ý rằng SHCreateDirectoryEx () không được dùng nữa và có thể bị xóa bất kỳ lúc nào khỏi các phiên bản Windows trong tương lai.
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
Thông thường dấu gạch chéo của Windows là dấu gạch chéo ngược. Điều sẽ xảy ra là người gọi nên làm sạch đường dẫn và đảm bảo tất cả các dấu gạch chéo đều phù hợp trước khi gọi phương thức này.
Tôi biết đó là một câu hỏi cũ nhưng nó hiển thị cao trên kết quả tìm kiếm của google và các câu trả lời được cung cấp ở đây không thực sự bằng C ++ hoặc hơi quá phức tạp.
Xin lưu ý rằng trong ví dụ của tôi, createDirTree () rất đơn giản vì tất cả các công việc nặng nhọc (kiểm tra lỗi, xác thực đường dẫn) cần phải được thực hiện bởi createDir (). Ngoài ra createDir () nên trả về true nếu thư mục đã tồn tại hoặc toàn bộ điều đó sẽ không hoạt động.
Đây là cách tôi sẽ làm điều đó trong 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;
}
Tất nhiên hàm createDir () sẽ dành riêng cho hệ thống và đã có đủ ví dụ trong các câu trả lời khác về cách viết nó cho linux, vì vậy tôi quyết định bỏ qua nó.
Vì vậy, nhiều cách tiếp cận đã được mô tả ở đây nhưng hầu hết chúng đều cần mã hóa cứng đường dẫn vào mã của bạn. Có một giải pháp dễ dàng cho vấn đề đó, sử dụng QDir và QFileInfo, hai lớp của Qt framework. Vì bạn đã ở trong môi trường Linux, nên dễ dàng sử dụng Qt.
QString qStringFileName("path/to/the/file/that/dont/exist.txt");
QDir dir = QFileInfo(qStringFileName).dir();
if(!dir.exists()) {
dir.mkpath(dir.path());
}
Đảm bảo rằng bạn có quyền ghi vào Đường dẫn đó.
mkdir -p /dir/to/the/file
touch /dir/to/the/file/thefile.ending
-p
lựa chọn là những gì tôi đang tìm kiếm. Cảm ơn!
Đây là hàm đệ quy C / C ++ sử dụng dirname()
để duyệt từ dưới lên của cây thư mục. Nó sẽ dừng ngay khi tìm thấy tổ tiên hiện có.
#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;
}
Những người khác đã cho bạn câu trả lời đúng, nhưng tôi nghĩ tôi sẽ chứng minh một điều gọn gàng khác mà bạn có thể làm:
mkdir -p /tmp/a/{b,c}/d
Sẽ tạo các đường dẫn sau:
/tmp/a/b/d
/tmp/a/c/d
Dấu ngoặc nhọn cho phép bạn tạo nhiều thư mục cùng một lúc trên cùng một cấp của hệ thống phân cấp, trong khi -p
tùy chọn có nghĩa là "tạo thư mục mẹ khi cần".