Рекурсивный системный вызов mkdir () в Unix
после прочтения справочной страницы mkdir(2) для системного вызова Unix с этим именем, кажется, что вызов не создает промежуточные каталоги в пути, только последний каталог в пути. Есть ли способ (или другая функция) создать все каталоги в пути, не прибегая к ручному разбору моей строки каталога и индивидуально создавая каждый каталог ?
14 ответов:
нет системного вызова, чтобы сделать это для вас, к сожалению. Я предполагаю, что это потому, что нет способа иметь действительно четко определенную семантику для того, что должно произойти в случаях ошибок. Должен ли он оставить каталоги, которые уже были созданы? Удалить их? Что делать, если удаление не удастся? И так далее...
Это довольно легко свернуть свой собственный, однако, и быстрый google для'рекурсивная функция mkdir' появилось несколько решений. Вот один, который был рядом с сверху:
http://nion.modprobe.de/blog/archives/357-Recursive-directory-creation.html
static void _mkdir(const char *dir) { char tmp[256]; char *p = NULL; size_t len; snprintf(tmp, sizeof(tmp),"%s",dir); len = strlen(tmp); if(tmp[len - 1] == '/') tmp[len - 1] = 0; for(p = tmp + 1; *p; p++) if(*p == '/') { *p = 0; mkdir(tmp, S_IRWXU); *p = '/'; } mkdir(tmp, S_IRWXU); }
вот мое решение. Вызывая функцию ниже, вы гарантируете, что все dir, ведущие к указанному пути к файлу, существуют. Обратите внимание, что - это не имя каталога, а скорее путь к файлу, который вы собираетесь создать после вызова
mkpath().например.,
mkpath("/home/me/dir/subdir/file.dat", 0755)создает/home/me/dir/subdirесли он не существует.mkpath("/home/me/dir/subdir/", 0755)делает то же самое.работает и с относительными путями.
возвращает
-1и телеаппаратурыerrnoв случае ошибка.int mkpath(char* file_path, mode_t mode) { assert(file_path && *file_path); char* p; for (p=strchr(file_path+1, '/'); p; p=strchr(p+1, '/')) { *p=''; if (mkdir(file_path, mode)==-1) { if (errno!=EEXIST) { *p='/'; return -1; } } *p='/'; } return 0; }отметим, что
file_pathизменяется во время действия, но затем восстанавливается. Поэтомуfile_pathне строгоconst.
вот еще один взгляд на
mkpath(), используя рекурсию, которая является одновременно маленькой и читаемой. Это делает использованиеstrdupa()чтобы избежать изменения данногоdirстроковый аргумент напрямую и во избежание использованияmalloc()&free(). Обязательно скомпилируйте с помощью-D_GNU_SOURCEактивироватьstrdupa()... это означает, что этот код работает только на GLIBC, EGLIBC, uClibc и других библиотеках C, совместимых с GLIBC.int mkpath(char *dir, mode_t mode) { if (!dir) { errno = EINVAL; return 1; } if (strlen(dir) == 1 && dir[0] == '/') return 0; mkpath(dirname(strdupa(dir)), mode); return mkdir(dir, mode); }после ввода как здесь, так и от Валерия Фролова, в проекте Inadyn были пересмотрены следующие версия
mkpath()теперь был нажат на libiteint mkpath(char *dir, mode_t mode) { struct stat sb; if (!dir) { errno = EINVAL; return 1; } if (!stat(dir, &sb)) return 0; mkpath(dirname(strdupa(dir)), mode); return mkdir(dir, mode); }он использует еще один syscall, но otoh код теперь более читаем.
взгляните на исходный код bash здесь, а конкретно смотрите в примерах / loadables / mkdir.c особенно строки 136-210. Если вы не хотите этого делать, вот некоторые из источников, которые имеют дело с этим (взяты прямо из смолы.gz, что я связал):
/* Make all the directories leading up to PATH, then create PATH. Note that this changes the process's umask; make sure that all paths leading to a return reset it to ORIGINAL_UMASK */ static int make_path (path, nmode, parent_mode) char *path; int nmode, parent_mode; { int oumask; struct stat sb; char *p, *npath; if (stat (path, &sb) == 0) { if (S_ISDIR (sb.st_mode) == 0) { builtin_error ("`%s': file exists but is not a directory", path); return 1; } if (chmod (path, nmode)) { builtin_error ("%s: %s", path, strerror (errno)); return 1; } return 0; } oumask = umask (0); npath = savestring (path); /* So we can write to it. */ /* Check whether or not we need to do anything with intermediate dirs. */ /* Skip leading slashes. */ p = npath; while (*p == '/') p++; while (p = strchr (p, '/')) { *p = ''; if (stat (npath, &sb) != 0) { if (mkdir (npath, parent_mode)) { builtin_error ("cannot create directory `%s': %s", npath, strerror (errno)); umask (original_umask); free (npath); return 1; } } else if (S_ISDIR (sb.st_mode) == 0) { builtin_error ("`%s': file exists but is not a directory", npath); umask (original_umask); free (npath); return 1; } *p++ = '/'; /* restore slash */ while (*p == '/') p++; } /* Create the final directory component. */ if (stat (npath, &sb) && mkdir (npath, nmode)) { builtin_error ("cannot create directory `%s': %s", npath, strerror (errno)); umask (original_umask); free (npath); return 1; } umask (original_umask); free (npath); return 0; }вы, вероятно, можете уйти с менее общей реализацией.
видимо, нет, мои два предложения:
char dirpath[80] = "/path/to/some/directory"; sprintf(mkcmd, "mkdir -p %s", dirpath); system(mkcmd);или если вы не хотите использовать
system()попробуйте посмотреть на coreutilsmkdirисходный код и посмотреть, как они реализовали .
мне не разрешено комментировать первый (и принятый) ответ (недостаточно rep), поэтому я опубликую свои комментарии в виде кода в новом ответе. Приведенный ниже код основан на первом ответе, но исправляет ряд проблем:
- если вызывается путь нулевой длины, это не считывает и не записывает символ перед началом массива
opath[](да, " почему ты так это называешь?", но с другой стороны "почему бы вам не исправить эту уязвимость?")- размер
opathтеперьPATH_MAX(что не идеально, но лучше, чем константа)- если путь длиннее или длиннее
sizeof(opath)затем он правильно завершается при копировании (которыйstrncpy()не делать)- вы можете указать режим письменного каталога, так же, как вы можете со стандартным
mkdir()(хотя, если вы укажите пользователя-запись, или не пользователь исполняемый рекурсия не работает)- main () возвращает (требуется?) int
- удалены несколько ненужных
#includes- мне больше нравится имя функции;)
// Based on http://nion.modprobe.de/blog/archives/357-Recursive-directory-creation.html #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <limits.h> static void mkdirRecursive(const char *path, mode_t mode) { char opath[PATH_MAX]; char *p; size_t len; strncpy(opath, path, sizeof(opath)); opath[sizeof(opath) - 1] = ''; len = strlen(opath); if (len == 0) return; else if (opath[len - 1] == '/') opath[len - 1] = ''; for(p = opath; *p; p++) if (*p == '/') { *p = ''; if (access(opath, F_OK)) mkdir(opath, mode); *p = '/'; } if (access(opath, F_OK)) /* if path is not terminated with / */ mkdir(opath, mode); } int main (void) { mkdirRecursive("/Users/griscom/one/two/three", S_IRWXU); return 0; }
мой рекурсивный способ сделать это:
#include <libgen.h> /* Only POSIX version of dirname() */ #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> static void recursive_mkdir(const char *path, mode_t mode) { char *spath = NULL; const char *next_dir = NULL; /* dirname() modifies input! */ spath = strdup(path); if (spath == NULL) { /* Report error, no memory left for string duplicate. */ goto done; } /* Get next path component: */ next_dir = dirname(spath); if (access(path, F_OK) == 0) { /* The directory in question already exists! */ goto done; } if (strcmp(next_dir, ".") == 0 || strcmp(next_dir, "/") == 0) { /* We reached the end of recursion! */ goto done; } recursive_mkdir(next_dir, mode); if (mkdir(path, mode) != 0) { /* Report error on creating directory */ } done: free(spath); return; }EDIT: исправлен мой старый фрагмент кода, ошибка-отчет по Namchester
два других ответа даны для
mkdir(1), а неmkdir(2)Как вы просите, но вы можете посмотреть исходный код для этой программы и посмотреть, как он реализует-pпараметры которая называетmkdir(2)повторно по мере необходимости.
мое решение:
int mkrdir(const char *path, int index, int permission) { char bf[NAME_MAX]; if(*path == '/') index++; char *p = strchr(path + index, '/'); int len; if(p) { len = MIN(p-path, sizeof(bf)-1); strncpy(bf, path, len); bf[len]=0; } else { len = MIN(strlen(path)+1, sizeof(bf)-1); strncpy(bf, path, len); bf[len]=0; } if(access(bf, 0)!=0) { mkdir(bf, permission); if(access(bf, 0)!=0) { return -1; } } if(p) { return mkrdir(path, p-path+1, permission); } return 0; }
вот мой выстрел в более общем решении:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> typedef int (*dirhandler_t)( const char*, void* ); /// calls itfunc for each directory in path (except for . and ..) int iterate_path( const char* path, dirhandler_t itfunc, void* udata ) { int rv = 0; char tmp[ 256 ]; char *p = tmp; char *lp = tmp; size_t len; size_t sublen; int ignore_entry; strncpy( tmp, path, 255 ); tmp[ 255 ] = ''; len = strlen( tmp ); if( 0 == len || (1 == len && '/' == tmp[ 0 ]) ) return 0; if( tmp[ len - 1 ] == '/' ) tmp[ len - 1 ] = 0; while( (p = strchr( p, '/' )) != NULL ) { ignore_entry = 0; *p = ''; lp = strrchr( tmp, '/' ); if( NULL == lp ) { lp = tmp; } else { lp++; } sublen = strlen( lp ); if( 0 == sublen ) /* ignore things like '//' */ ignore_entry = 1; else if( 1 == sublen && /* ignore things like '/./' */ '.' == lp[ 0 ] ) ignore_entry = 1; else if( 2 == sublen && /* also ignore things like '/../' */ '.' == lp[ 0 ] && '.' == lp[ 1 ] ) ignore_entry = 1; if( ! ignore_entry ) { if( (rv = itfunc( tmp, udata )) != 0 ) return rv; } *p = '/'; p++; lp = p; } if( strcmp( lp, "." ) && strcmp( lp, ".." ) ) return itfunc( tmp, udata ); return 0; } mode_t get_file_mode( const char* path ) { struct stat statbuf; memset( &statbuf, 0, sizeof( statbuf ) ); if( NULL == path ) { return 0; } if( 0 != stat( path, &statbuf ) ) { fprintf( stderr, "failed to stat '%s': %s\n", path, strerror( errno ) ); return 0; } return statbuf.st_mode; } static int mymkdir( const char* path, void* udata ) { (void)udata; int rv = mkdir( path, S_IRWXU ); int errnum = errno; if( 0 != rv ) { if( EEXIST == errno && S_ISDIR( get_file_mode( path ) ) ) /* it's all good, the directory already exists */ return 0; fprintf( stderr, "mkdir( %s ) failed: %s\n", path, strerror( errnum ) ); } // else // { // fprintf( stderr, "created directory: %s\n", path ); // } return rv; } int mkdir_with_leading( const char* path ) { return iterate_path( path, mymkdir, NULL ); } int main( int argc, const char** argv ) { size_t i; int rv; if( argc < 2 ) { fprintf( stderr, "usage: %s <path> [<path>...]\n", argv[ 0 ] ); exit( 1 ); } for( i = 1; i < argc; i++ ) { rv = mkdir_with_leading( argv[ i ] ); if( 0 != rv ) return rv; } return 0; }
очень простое решение, просто передайте вход:
mkdir dirnamevoid execute_command_mkdir(char *input) { char rec_dir[500]; int s; if(strcmp(input,"mkdir") == 0) printf("mkdir: operand required"); else { char *split = strtok(input," \t"); while(split) { if(strcmp(split,"create_dir") != 0) strcpy(rec_dir,split); split = strtok(NULL, " \t"); } char *split2 = strtok(rec_dir,"/"); char dir[500]; strcpy(dir, ""); while(split2) { strcat(dir,split2); strcat(dir,"/"); printf("%s %s\n",split2,dir); s = mkdir(dir,0700); split2 = strtok(NULL,"/"); } strcpy(output,"ok"); } if(s < 0) printf(output,"Error!! Cannot Create Directory!!"); }
совсем прям. Это может быть хорошей отправной точкой
int makeDir(char *fullpath, mode_t permissions){ int i=0; char *arrDirs[20]; char aggrpaz[255]; arrDirs[i] = strtok(fullpath,"/"); strcpy(aggrpaz, "/"); while(arrDirs[i]!=NULL) { arrDirs[++i] = strtok(NULL,"/"); strcat(aggrpaz, arrDirs[i-1]); mkdir(aggrpaz,permissions); strcat(aggrpaz, "/"); } i=0; return 0; }вы анализируете эту функцию полный путь плюс разрешения, которые вы хотите, т. е. S_IRUSR, полный список режимов смотрите здесь https://techoverflow.net/2013/04/05/how-to-use-mkdir-from-sysstat-h/
строка fullpath будет разделена символом " / " и отдельные dirs будут добавлены к aggrpaz строка по одному. Каждой итерации цикла вызывается функция mkdir функция, передавая ему агрегатный путь до сих пор плюс разрешения. Этот пример можно улучшить, я не проверяю вывод функции mkdir, и эта функция работает только с абсолютными путями.
Comments