Chcę mieć łatwy sposób tworzenia wielu katalogów w C ++ / Linuxie.
Na przykład chcę zapisać plik lola.file w katalogu:
/tmp/a/b/c
ale jeśli nie ma katalogów, chcę, aby były tworzone automagicznie. Działający przykład byłby doskonały.
Chcę mieć łatwy sposób tworzenia wielu katalogów w C ++ / Linuxie.
Na przykład chcę zapisać plik lola.file w katalogu:
/tmp/a/b/c
ale jeśli nie ma katalogów, chcę, aby były tworzone automagicznie. Działający przykład byłby doskonały.
Odpowiedzi:
W C ++ 17 lub nowszym istnieje standardowy nagłówek <filesystem>
z funkcją,
std::filesystem::create_directories
który powinien być używany we współczesnych programach C ++. Standardowe funkcje C ++ nie mają jednak specyficznego dla POSIX jawnego argumentu permissions (mode).
Jednak tutaj jest funkcja C, którą można skompilować za pomocą kompilatorów 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 */
Makra STRDUP()
i FREE()
są wersjami sprawdzającymi błędy programu
strdup()
i free()
, zadeklarowanymi w emalloc.h
(i zaimplementowanymi w
emalloc.c
i estrdup.c
). Do "sysstat.h"
oferty nagłówek z rozbitych wersjach <sys/stat.h>
i może być zastąpiony <sys/stat.h>
na nowoczesnych systemach Unix (ale było wiele problemów z powrotem w 1990 roku). I "mkpath.h"
oświadczamkpath()
.
Zmiana między wersją 1.12 (oryginalna wersja odpowiedzi) a wersją 1.13 (poprawiona wersja odpowiedzi) była testem EEXIST
w
do_mkdir()
. Zwrócił na to uwagę
Switch - dziękuję Switch. Kod testowy został zaktualizowany i odtworzył problem na MacBooku Pro (2,3 GHz Intel Core i7, z systemem Mac OS X 10.7.4) i sugeruje, że problem został rozwiązany w wersji (ale testy mogą tylko wykazać obecność błędów , nigdy ich nieobecność). Wyświetlany kod to teraz wersja 1.16; od wersji 1.13 wprowadzono zmiany kosmetyczne lub administracyjne (takie jak używanie mkpath.h
zamiast jlss.h
i <unistd.h>
bezwarunkowe uwzględnianie tylko w kodzie testowym). Rozsądnie jest argumentować, że "sysstat.h"
należy go zastąpić,
<sys/stat.h>
chyba że masz niezwykle oporny system.
(Otrzymujesz zgodę na użycie tego kodu w dowolnym celu z podaniem źródła).
Ten kod jest dostępny w moim
repozytorium SOQ (Stack Overflow Questions) na GitHub jako pliki mkpath.c
i
mkpath.h
(itp.) W
podkatalogu src / so-0067-5039 .
if (errno != EEXIST) { status = -1; }
gdy mkdir zawiedzie.
stat()
wcześniej mkdir()
; jest to problem TOCTOU (czas sprawdzenia, czas użycia). Próbowałem łaskotać błąd skryptem powłoki uruchamiającym 13 procesów w tle, tworząc tę samą 29-elementową ścieżkę i nie udało mi się go trafić. Następnie włamałem się do programu testowego, aby rozwidlić 20 razy i kazałem każdemu dziecku spróbować, a to udało się trafić w błąd. Naprawiony kod będzie miał if (mkdir(path, mode) != 0 && errno != EEXIST) status = -1;
. To nie pokazuje błędu.
jlss.h
, emalloc.h
), a nie biblioteki. Jednak kod jest dostępny w moim SOQ (przepełnienie stosu pytań) repozytorium na GitHub jako pliki jlss.h
, emalloc.c
a emalloc.h
w libsoq / src podkatalogu. Będziesz też potrzebować posixver.h
i kilka innych ( debug.h
,stderr.c
, stderr.h
- Myślę, że to wszystko, ale to, czego potrzebujesz powinny być w tym katalogu).
Łatwe dzięki Boost. create_directories
#include <boost/filesystem.hpp>
//...
boost::filesystem::create_directories("/tmp/a/b/c");
Zwraca: true
jeśli utworzono nowy katalog, w przeciwnym razie 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")
to najkrótszy sposób, jaki mogę sobie wyobrazić (pod względem długości kodu, niekoniecznie czasu wykonania).
Nie jest to platforma wieloplatformowa, ale będzie działać pod Linuksem.
#include <sys/types.h>
#include <sys/stat.h>
int status;
...
status = mkdir("/tmp/a/b/c", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
Od tutaj . Może być konieczne utworzenie oddzielnych katalogów mkdir dla / tmp, / tmp / a, / tmp / a / b /, a następnie / tmp / a / b / c, ponieważ nie ma odpowiednika opcji -p w interfejsie API C. Upewnij się i zignoruj EEXISTS errno podczas wykonywania zadań wyższego poziomu.
("/tmp/",...)
, ("/tmp/a/",...)
, ("/tmp/a/b/",...)
,("/tmp/a/b/c/",...)
Oto mój przykład kodu (działa zarówno w systemie Windows, jak i 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;
}
Stosowanie:
$ makePath 1/2 folderA/folderB/folderC
creating 1/2 ... OK
creating folderA/folderB/folderC ... OK
stat
(związane z __STDC__
) nie wymagają testu prekompilatora.
Należy zauważyć, że począwszy od systemu plików C ++ 17 interfejs jest częścią standardowej biblioteki. Oznacza to, że można utworzyć katalogi:
#include <filesystem>
std::filesystem::create_directories("/a/b/c/d")
Więcej informacji tutaj: https://en.cppreference.com/w/cpp/filesystem/create_directory
Dodatkowo, z gcc, trzeba "-std = c ++ 17" do CFLAGS. I „-lstdc ++ fs” do LDLIBS. Ten ostatni potencjalnie nie będzie potrzebny w przyszłości.
Jest to podobne do poprzedniego, ale działa w przód przez łańcuch zamiast rekurencyjnie do tyłu. Pozostawia errno odpowiednią wartość dla ostatniej awarii. Jeśli występuje wiodący ukośnik, istnieje dodatkowy czas w pętli, którego można było uniknąć za pomocą funkcji find_first_of () poza pętlą lub przez wykrycie wiodącego / i ustawienie pre na 1. Wydajność jest taka sama, niezależnie od tego, czy zostaniemy skonfigurowani przez pierwsza pętla lub wywołanie przed pętlą, a złożoność byłaby (nieco) większa podczas korzystania z wywołania przed pętlą.
#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';
}
Powiedziałeś „C ++”, ale wydaje się, że wszyscy tutaj myślą „powłoka bash”.
Sprawdź kod źródłowy GNU mkdir
; wtedy możesz zobaczyć, jak zaimplementować polecenia powłoki w 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;
}
Więc potrzebuję mkdirp()
dzisiaj, a rozwiązania na tej stronie okazały się zbyt skomplikowane. Dlatego napisałem dość krótki fragment, który można łatwo skopiować dla innych, którzy natkną się na ten wątek, i zastanawiam się, dlaczego potrzebujemy tak wielu wierszy kodu.
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;
}
Jeśli nie lubisz rzucać const i tymczasowo modyfikować łańcucha, po prostu zrób a strdup()
i free()
potem.
Ponieważ ten post zajmuje wysokie miejsce w Google w kategorii „Utwórz drzewo katalogów”, zamierzam opublikować odpowiedź, która będzie działać w systemie Windows - będzie działać przy użyciu interfejsu API Win32 skompilowanego dla UNICODE lub MBCS. Jest to przeniesione z powyższego kodu Marka.
Ponieważ jest to system Windows, z którym pracujemy, separatorami katalogów są ukośniki BACK, a nie ukośniki w przód. Jeśli wolisz ukośniki, zmień '\\'
na'/'
Będzie działać z:
c:\foo\bar\hello\world
i
c:\foo\bar\hellp\world\
(tj .: nie wymaga końcowego ukośnika, więc nie musisz go sprawdzać).
Zanim powiesz „Po prostu użyj SHCreateDirectoryEx () w Windows”, zwróć uwagę, że SHCreateDirectoryEx () jest przestarzała i może zostać usunięta w dowolnym momencie z przyszłych wersji systemu 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
Zazwyczaj ukośniki systemu Windows są ukośnikami odwrotnymi. Powinno się zdarzyć, że wywołujący powinien oczyścić ścieżkę i upewnić się, że wszystkie ukośniki są prawidłowe przed wywołaniem tej metody.
Wiem, że to stare pytanie, ale pojawia się wysoko w wynikach wyszukiwania Google, a podane tutaj odpowiedzi nie są tak naprawdę w C ++ lub są nieco zbyt skomplikowane.
Zwróć uwagę, że w moim przykładzie createDirTree () jest bardzo proste, ponieważ całe podnoszenie ciężarów (sprawdzanie błędów, walidacja ścieżki) i tak musi być wykonane przez createDir (). Również createDir () powinno zwrócić true, jeśli katalog już istnieje lub całość nie zadziała.
Oto jak zrobiłbym to w 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;
}
Oczywiście funkcja createDir () będzie specyficzna dla systemu, aw innych odpowiedziach jest już wystarczająco dużo przykładów, jak napisać ją dla Linuksa, więc zdecydowałem się ją pominąć.
Opisano tutaj tak wiele podejść, ale większość z nich wymaga twardego zakodowania ścieżki do kodu. Istnieje proste rozwiązanie tego problemu, używając QDir i QFileInfo, dwóch klas frameworka Qt. Ponieważ jesteś już w środowisku Linux, korzystanie z Qt powinno być łatwe.
QString qStringFileName("path/to/the/file/that/dont/exist.txt");
QDir dir = QFileInfo(qStringFileName).dir();
if(!dir.exists()) {
dir.mkpath(dir.path());
}
Upewnij się, że masz dostęp do zapisu w tej ścieżce.
mkdir -p /dir/to/the/file
touch /dir/to/the/file/thefile.ending
-p
rozwiązaniem jest to, czego szukam. Dzięki!
Oto rekurencyjna funkcja C / C ++, która wykorzystuje dirname()
do przechodzenia z dołu do góry drzewa katalogów. Zatrzyma się, gdy tylko znajdzie istniejącego przodka.
#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;
}
Inni dali ci właściwą odpowiedź, ale pomyślałem, że zademonstruję inną fajną rzecz, którą możesz zrobić:
mkdir -p /tmp/a/{b,c}/d
Stworzy następujące ścieżki:
/tmp/a/b/d
/tmp/a/c/d
Nawiasy umożliwiają tworzenie wielu katalogów jednocześnie na tym samym poziomie hierarchii, podczas gdy -p
opcja oznacza „utwórz katalogi nadrzędne według potrzeb”.