Unix 上的递归 mkdir() 系统调用

Posted

技术标签:

【中文标题】Unix 上的递归 mkdir() 系统调用【英文标题】:Recursive mkdir() system call on Unix 【发布时间】:2011-01-21 03:07:22 【问题描述】:

在阅读了具有该名称的 Unix 系统调用的 mkdir(2) 手册页后,该调用似乎不会在路径中创建中间目录,而只会在路径中创建最后一个目录。有什么方法(或其他功能)可以创建路径中的所有目录,而无需手动解析我的目录字符串并单独创建每个目录?

【问题讨论】:

【参考方案1】:

不幸的是,没有系统调用可以为您执行此操作。我猜那是因为没有办法为错误情况下应该发生的事情提供真正明确定义的语义。它应该离开已经创建的目录吗?删除它们?如果删除失败怎么办?等等……

不过,自己动手做起来很容易,快速谷歌搜索“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);

【讨论】:

我唯一要改变的是 tmp[256] 到 tmp[PATH_MAX] 还有 #include 改进版:gist.github.com/JonathonReinhart/… snprintf() 返回格式化字符串的长度,对strlen() 的调用是多余的。 len = snprintf(tmp, ... 。您可以通过if(len >= sizeof tmp) 来检查缓冲区溢出。使用 strlen() 是不可能的。 mkdir in loop 创建目录失败怎么办?例如权限?我强烈推荐来自 github gist.github.com/JonathonReinhart/… 的提到的版本 @rouzier PATH_MAX 可能不是一个改进,因为 PATH_MAX 不会在 POSIX-compliant systems where the value varies between different file systems 上定义(粗体字):“以下列表中符号常量之一的定义 应从<limits.h> 标头中省略,具体实现中对应的值等于或大于规定的最小值,但该值可能因应用它的文件而异。”跨度> 【参考方案2】:

嗯,我认为 mkdir -p 可以做到这一点?

mkdir -p this/is/a/full/path/of/stuff

【讨论】:

是的,但问题与 C 函数调用有关。 确实 - 赞成票大概反映了这对许多人来说是一个有用的答案,但它是对与所提出问题不同的问题的答案 然而,您可以查看 mkdir 的源代码以了解 it 是如何做到的。快速google了一下,相关代码似乎在mkancestdirs.ccoreutils 这是不同问题的答案。 @gamen,您评论中的链接已过时。【参考方案3】:

这是我的解决方案。通过调用下面的函数,您可以确保所有指向指定文件路径的目录都存在。请注意,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 要求。 @FelipeTonello 您可以按照其他答案中的建议轻松分配内存并复制参数。我的目标是性能,所以我尽量避免昂贵的操作,例如内存分配。【参考方案4】:

这是对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 时不会泄漏内存吗? Nope :) 当函数脱离上下文时,strdupa() 分配的内存(在堆栈上)会自动释放。有关详细信息,请参阅手册页。 啊,对不起,你是对的。我一直在想strdup。 :) 我知道这是一个非常古老的线程,但我一直在寻找解决方案,我最喜欢这个答案是我所见过的一切。但是,这不适用于相对路径。添加对 dir[0] == '.' 的检查检查 dir[0]=='/' 会纠正这个问题,因为这就是 dirname 的最终结果。 这仍然不理想,因为它试图创建“..”文件夹,但这只是返回一个文件存在错误并继续。你也可以先做一个 mkdir,只有当 mkdir 返回 ENOENT 时才递归。缺点是你必须在方法中调用 mkdir 两次。【参考方案5】:

查看 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;

您可能可以使用不太通用的实现。

【讨论】:

【参考方案6】:

显然不是,我的两个建议是:

char dirpath[80] = "/path/to/some/directory";
sprintf(mkcmd, "mkdir -p %s", dirpath);
system(mkcmd);

或者,如果您不想使用 system(),请尝试查看 coreutils mkdir 源代码,看看他们如何实现 -p 选项。

【讨论】:

OMG - 现在是 2014 年,文件路径现在通常有空格。请不要这样编码 @Lothar 也许你没有意识到这个答案是 4 年多以前做出的,所以OMG - it's 2010 会更贴切。也许只有我一个人,但在%s 周围没有引用似乎不太适合将神加入其中。如果您想提出修改建议,请随时提出。 @Lothar 首先使用 system() 也是一个错误的选择,不使用 mkdir 的绝对路径的安全漏洞,以及错误编码的 'char dirpath[]' 行;每条线都有问题,所以不得不投反对票;抱歉 SiegeX,但这是一个非常糟糕的答案,但正如你所说,那是在 2010 年;) /bin/sh 将使用 PATH 环境变量来定位 mkdir。如果名为 mkdir 的可执行文件位于 /bin 之前的路径位置,则会改为执行。 哎呀缓冲区溢出。而且您最好希望没有人创建以 ;rm -rf ~ 结尾的路径【参考方案7】:

我不允许评论第一个(和接受的)答案(没有足够的代表),所以我会将我的 cmets 作为代码发布在一个新的答案中。下面的代码基于第一个答案,但修复了一些问题:

如果使用零长度路径调用,则不会读取或写入数组 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] = '\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;

【讨论】:

我会将其更改为返回 int。所以重构只需 1 个字。【参考方案8】:

我这样做的递归方式:

#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 报告的错误

【讨论】:

@Jeff,是的。有什么问题吗?在这种情况下,看起来比大量的 if 或类似的东西更容易理解。它可读且运行良好。 不创建最后一个目录。如果recursive_mkdir("/home/test/hello",mode),不创建hello目录。 @Namchester,显然它不适用于绝对路径。修复。【参考方案9】:

给出的另外两个答案是针对mkdir(1) 而不是您要求的mkdir(2),但您可以查看该程序的the source code 并查看它如何实现重复调用mkdir(2)-p 选项根据需要。

【讨论】:

实际的问题是“有没有办法(或其他功能)来创建路径中的所有目录,而无需手动解析我的目录字符串并单独创建每个目录”所以 mkdir(1) 是另一种方式! make_dir_parents() 函数可能是最有趣的部分,但它不在那个文件中。它位于mkdir-p.c in the gnulib repository。【参考方案10】:

我的解决方案:

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;

【讨论】:

【参考方案11】:

这是我对更通用解决方案的看法:

#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;

【讨论】:

【参考方案12】:

一个非常简单的解决方案,只需传入输入: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!!");

【讨论】:

【参考方案13】:

很直接。这可能是一个很好的起点

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 函数的输出,这个函数只适用于绝对路径。

【讨论】:

【参考方案14】:

这是我的解决方案

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")

【讨论】:

【参考方案15】:

如果你喜欢递归是因为它很有趣!

#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;

【讨论】:

以上是关于Unix 上的递归 mkdir() 系统调用的主要内容,如果未能解决你的问题,请参考以下文章

3-递归调用

linux 内核通过啥系统调用,来与硬件交互?

Unix系统编程通用的I/O

Unix系统调用

iOS4上的C语言毫秒睡眠

SQL Server 在生产服务器上的函数之间的递归调用失败