Unix上的recursionmkdir()系统调用
在读取具有该名称的Unix系统调用的mkdir(2)手册页之后,似乎该调用不会在path中创build中间目录,而只会创buildpath中的最后一个目录。 有什么办法(或其他function)来创buildpath中的所有目录,而不诉诸手动parsing我的目录string,并单独创build每个目录?
不幸的是,没有系统调用来为你做。 我猜这是因为没有一种方法可以为错误情况下发生的情况提供非常明确的语义。 它应该离开已经创build的目录吗? 删除它们? 如果删除失败怎么办? 等等…
然而,推出自己的产品是相当容易的,而对于“ recursionmkdir ”的快速谷歌提出了许多解决scheme。 这是一个靠近顶部的:
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); }
嗯,我认为,mkdir -p呢?
mkdir -p this / is / a / full / path / of / stuff
这是我的解决scheme。 通过调用下面的函数确保所有指向文件path的目录都存在。 请注意, file_path
参数在这里不是目录名称,而是调用mkpath()
后要创build的文件的path。
例如, mkpath("/home/me/dir/subdir/file.dat", 0755)
将创build/home/me/dir/subdir
如果不存在)。 mkpath("/home/me/dir/subdir/", 0755)
也一样。
与相对path一起工作。
如果发生错误,则返回-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='\0'; if (mkdir(file_path, mode)==-1) { if (errno!=EEXIST) { *p='/'; return -1; } } *p='/'; } return 0; }
请注意, file_path
在操作期间被修改,但之后得到恢复。 因此file_path
不是严格的const
。
看看这里的bash源代码,特别是看看examples / loadables / mkdir.c,尤其是第136-210行。 如果你不想这样做,下面是一些处理这个问题的源代码(直接从我连接的tar.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 = '\0'; 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; }
你可能会逃避一个不太普遍的实现。
下面是另一个关于mkpath()
,它使用recursion,它既小又可读。 它使用strdupa()
来避免直接改变给定的dir
string参数,并避免使用malloc()
和free()
。 确保使用-D_GNU_SOURCE
进行编译以激活strdupa()
…意味着此代码只能在GLIBC,EGLIBC,uClibc和其他兼容GLIBC的C库上工作。
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项目中input了这里和Valery Frolov的input之后,现在mkpath()
的以下修订版本推到了libite
int 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); }
它使用了一个更多的系统调用,但是现在它的代码更易读。
显然不是,我的两个build议是:
char dirpath[80] = "/path/to/some/directory"; sprintf(mkcmd, "mkdir -p %s", dirpath); system(mkcmd);
或者,如果您不想使用system()
尝试查看coreutils mkdir
源代码,并查看它们是如何实现-p
选项的。
其实你可以使用:
mkdir -p ./some/directories/to/be/created/
我不允许评论第一个(也是被接受的)答案(没有足够的代表),所以我会把我的评论作为代码张贴在一个新的答案中。 下面的代码是基于第一个答案,但是修复了一些问题:
- 如果使用零长度path调用,则不会在数组
opath[]
开始之前读取或写入字符(是的,“为什么要这样调用?”,但另一方面“为什么不修复该漏洞?“) -
opath
的大小现在是PATH_MAX
(这不是完美的,但是比常数要好) - 如果path与
sizeof(opath)
一样长或更长,则在复制时正确终止(strncpy()
不执行) - 您可以像使用标准
mkdir()
一样指定写入目录的模式(尽pipe如果指定非用户可写或非用户可执行,则recursion将不起作用) - main()返回(required?)int
- 删除了一些不必要的
#include
s - 我更喜欢函数名称;)
// 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] = '\0'; len = strlen(opath); if (len == 0) return; else if (opath[len - 1] == '/') opath[len - 1] = '\0'; for(p = opath; *p; p++) if (*p == '/') { *p = '\0'; 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; }
我这样做的recursion方式:
#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; }
编辑:修复了我的旧代码片段,由Namchester错误报告
给出的另外两个答案是针对mkdir(1)
而不是mkdir(2)
,但您可以查看该程序的源代码 ,并查看它如何实现-p
选项,该选项将mkdir(2)
重复调用为需要。
我的解决scheme
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; }
这是我的一个更通用的解决scheme:
#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 ] = '\0'; 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 = '\0'; 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; }
一个非常简单的解决scheme,只需要input: mkdir dirname
void 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; }
你parsing这个函数一个完整的path加上你想要的权限,即S_IRUSR ,为模式的完整列表去https://techoverflow.net/2013/04/05/how-to-use-mkdir-from-sysstat- H/
完整pathstring将被“/”字符分割,并且个别目录将被一次一个地附加到聚合string。 每个循环迭代都会调用mkdir函数,并将权限传递给聚合path。 这个例子可以改进,我没有检查mkdir函数的输出,这个函数只适用于绝对path。