【发布时间】:2011-01-21 03:07:22
【问题描述】:
在阅读了具有该名称的 Unix 系统调用的 mkdir(2) 手册页后,该调用似乎不会在路径中创建中间目录,而只会在路径中创建最后一个目录。有什么方法(或其他功能)可以创建路径中的所有目录,而无需手动解析我的目录字符串并单独创建每个目录?
【问题讨论】:
在阅读了具有该名称的 Unix 系统调用的 mkdir(2) 手册页后,该调用似乎不会在路径中创建中间目录,而只会在路径中创建最后一个目录。有什么方法(或其他功能)可以创建路径中的所有目录,而无需手动解析我的目录字符串并单独创建每个目录?
【问题讨论】:
不幸的是,没有系统调用可以为您执行此操作。我猜那是因为没有办法为错误情况下应该发生的事情提供真正明确定义的语义。它应该离开已经创建的目录吗?删除它们?如果删除失败怎么办?等等……
不过,自己动手做起来很容易,快速谷歌搜索“recursive 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);
}
【讨论】:
snprintf() 返回格式化字符串的长度,对strlen() 的调用是多余的。 len = snprintf(tmp, ... 。您可以通过if(len >= sizeof tmp) 来检查缓冲区溢出。使用 strlen() 是不可能的。
PATH_MAX 可能不是一个改进,因为 PATH_MAX 不会在 POSIX-compliant systems where the value varies between different file systems 上定义(粗体字):“以下列表中符号常量之一的定义 应从<limits.h> 标头中省略,具体实现中对应的值等于或大于规定的最小值,但该值可能因应用它的文件而异。”跨度>
嗯,我认为 mkdir -p 可以做到这一点?
mkdir -p this/is/a/full/path/of/stuff
【讨论】:
coreutils中
这是我的解决方案。通过调用下面的函数,您可以确保所有指向指定文件路径的目录都存在。请注意,file_path 参数不是此处的目录名称,而是您将在调用 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);
for (char* 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。
【讨论】:
char *作为参数,因为它改变了原始指针的内容。这并不理想,因为它不适用于静态常量字符串,例如,它有不必要的 API 要求。
这是对mkpath() 的另一种看法,使用递归,既小又易读。它使用strdupa() 来避免直接更改给定的dir 字符串参数并避免使用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);
}
在此处和 Valery Frolov 输入后,在 Inadyn 项目中,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);
}
它使用了更多的系统调用,但现在代码更具可读性。
【讨论】:
strdupa 时不会泄漏内存吗?
strdupa() 分配的内存(在堆栈上)会自动释放。有关详细信息,请参阅手册页。
strdup。 :)
查看 bash 源代码 here,并特别查看示例/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;
}
您可能可以使用不太通用的实现。
【讨论】:
显然不是,我的两个建议是:
char dirpath[80] = "/path/to/some/directory";
sprintf(mkcmd, "mkdir -p %s", dirpath);
system(mkcmd);
或者,如果您不想使用 system(),请尝试查看 coreutils mkdir 源代码,看看他们如何实现 -p 选项。
【讨论】:
OMG - it's 2010 会更贴切。也许只有我一个人,但在%s 周围没有引用似乎不太适合将神加入其中。如果您想提出修改建议,请随时提出。
我不允许评论第一个(和接受的)答案(没有足够的代表),所以我会将我的 cmets 作为代码发布在一个新的答案中。下面的代码基于第一个答案,但修复了一些问题:
opath[] 开头之前的字符(是的,“你为什么要这样称呼它?”,但另一方面,“为什么你不修复漏洞?”)opath 的大小现在是 PATH_MAX(这并不完美,但比一个常数要好)sizeof(opath) 一样长或更长,则复制时会正确终止(strncpy() 不会这样做)mkdir() 一样(尽管如果您指定非用户可写或非用户可执行,则递归将不起作用)#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] = '\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;
}
【讨论】:
我这样做的递归方式:
#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;
}
编辑:修复了我的旧代码 sn-p,Namchester 报告的错误
【讨论】:
给出的另外两个答案是针对mkdir(1) 而不是您要求的mkdir(2),但您可以查看该程序的the source code 并查看它如何实现重复调用mkdir(2) 的-p 选项根据需要。
【讨论】:
make_dir_parents() 函数可能是最有趣的部分,但它不在那个文件中。它位于mkdir-p.c in the gnulib repository。
我的解决方案:
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 ] = '\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;
}
【讨论】:
一个非常简单的解决方案,只需传入输入: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;
}
您将此函数解析为完整路径以及您想要的权限,即 S_IRUSR,有关模式的完整列表,请转到此处https://techoverflow.net/2013/04/05/how-to-use-mkdir-from-sysstat-h/
完整路径字符串将由“/”字符分隔,并且单个目录将一次附加到 aggrpaz 字符串。每次循环迭代都会调用 mkdir 函数,将到目前为止的聚合路径以及权限传递给它。这个例子可以改进,我没有检查 mkdir 函数的输出,这个函数只适用于绝对路径。
【讨论】:
这是我的解决方案
void mkpath(char *p) {
char *path = strdup(p);
char *save_path = path;
char *sep1;
char *sep2=0;
do {
int idx = (sep2-path)<0 ? 0 : sep2-path;
sep1 = strchr(path + idx , '/');
sep2 = strchr(sep1+1, '/');
if (sep2) {
path[sep2-path]=0;
}
if(mkdir(path, 0777) && errno != EEXIST)
break;
if (sep2) {
path[sep2-path]='/';
}
} while (sep2);
free(save_path);
}
.
.
.
mkpath ("./the/new/path")
【讨论】:
如果你喜欢递归是因为它很有趣!
#include <string.h>
#include <sys/stat.h> /* mkdir(2) */
#include <limits.h> /* PATH_MAX */
int mkdirp(const char *dir, const mode_t mode){
struct stat sb;
//if dir already exists and is a directory
if (stat(dir, &sb) == 0){
if (S_ISDIR(sb.st_mode)) {
return 0;
}
else return -1;
}
else {
char tmp[PATH_MAX];
size_t len = strnlen(dir, PATH_MAX);
memcpy(tmp, dir, len);
//remove trailing slash
if (tmp[len-1]=='/'){
tmp[len-1]='\0';
}
char *p = strrchr(tmp, '/');
*p='\0';
int ret = mkdirp(tmp, mode);
if (ret == 0){
return mkdir(dir, mode);
}
}
return 0;
}
【讨论】: