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

时间:2010-02-25 17:41:47

标签: c posix unix mkdir

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

16 个答案:

答案 0 :(得分:81)

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

很容易推出自己的版本,而快速谷歌“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);
}

答案 1 :(得分:66)

嗯,我以为mkdir -p可以做到吗?

  

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

答案 2 :(得分:19)

这是我的解决方案。通过调用下面的函数,您可以确保存在指向文件路径的所有dirs。请注意,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); 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并非严格{{1}}。

答案 3 :(得分:11)

这是对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);
}

它再使用一个系统调用,但是现在代码更易读。

答案 4 :(得分:9)

看一下bash源代码here,具体看看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;
}

你可能可以通过不那么普遍的实现来逃避。

答案 5 :(得分:8)

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

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

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

答案 6 :(得分:2)

实际上你可以使用:

mkdir -p ./some/directories/to/be/created/

答案 7 :(得分:1)

我这样做的递归方式:

#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

的错误报告

答案 8 :(得分:1)

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

  • 如果使用零长度路径调用,则不会在数组opath[]开头之前读取或写入字符(是的,“为什么要这样称呼它?”,但另一方面“你为什么不修复漏洞?“)
  • opath的大小现在为PATH_MAX(这不是完美的,但优于常数)
  • 如果路径与sizeof(opath)一样长或更长,则在复制时(strncpy()不执行)正确终止
  • 您可以使用标准mkdir()指定写入目录的模式(尽管如果指定非用户可写或非用户可执行,则递归将不起作用)< / LI>
  • 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;
}

答案 9 :(得分:0)

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

答案 10 :(得分:0)

我的解决方案:

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 :(得分: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;
}

答案 12 :(得分: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!!");
}

答案 13 :(得分:0)

挺直的。这可以是一个很好的起点

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 :(得分:0)

这是我的解决方案

                           numeric_    character_
Obs    id       date        feature     feature       next_date

  1     1    2020-01-01        5           A         2020-01-02
  2     1    2020-01-02        3           Z         2020-01-04
  3     1    2020-01-03        3           Z         2020-01-04
  4     1    2020-01-04        2           D         2020-01-05
  5     1    2020-01-05        7           B         2020-01-01
  6     2    2020-01-01        4           V         2020-01-03
  7     2    2020-01-02        4           V         2020-01-03
  8     2    2020-01-03        1           B         2020-01-05
  9     2    2020-01-04        1           B         2020-01-05
 10     2    2020-01-05        9           F                  .

答案 15 :(得分:0)

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

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