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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2336242/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 04:37:28  来源:igfitidea点击:

Recursive mkdir() system call on Unix

cposixunixmkdir

提问by Alex Marshall

After reading the mkdir(2) man page for the Unix system call with that name, it appears that the call doesn't create intermediate directories in a path, only the last directory in the path. Is there any way (or other function) to create all the directories in the path without resorting to manually parsing my directory string and individually creating each directory ?

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

回答by Carl Norum

There is not a system call to do it for you, unfortunately. I'm guessing that's because there isn't a way to have really well-defined semantics for what should happen in error cases. Should it leave the directories that have already been created? Delete them? What if the deletions fail? And so on...

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

It is pretty easy to roll your own, however, and a quick google for 'recursive mkdir' turned up a number of solutions. Here's one that was near the top:

然而,推出自己的产品非常容易,并且在 google 上快速搜索“递归 mkdir”,找到了许多解决方案。这是靠近顶部的一个:

http://nion.modprobe.de/blog/archives/357-Recursive-directory-creation.html

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

回答by j03m

hmm I thought that mkdir -p does that?

嗯,我以为 mkdir -p 是这样做的?

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

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

回答by Yaroslav Stavnichiy

Here is my solution. By calling the function below you ensure that all dirs leading to the file path specified exist. Note that file_pathargument is not directory name here but rather a path to a file that you are going to create after calling mkpath().

这是我的解决方案。通过调用下面的函数,您可以确保所有指向指定文件路径的目录都存在。请注意,file_path这里的参数不是目录名称,而是您将在调用mkpath().

Eg., mkpath("/home/me/dir/subdir/file.dat", 0755)shall create /home/me/dir/subdirif it does not exist. mkpath("/home/me/dir/subdir/", 0755)does the same.

例如,如果它不存在,mkpath("/home/me/dir/subdir/file.dat", 0755)则应创建/home/me/dir/subdirmkpath("/home/me/dir/subdir/", 0755)做同样的事情。

Works with relative paths as well.

也适用于相对路径。

Returns -1and sets errnoin case of an error.

发生错误时返回-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 = '
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);
}
'; if (mkdir(file_path, mode) == -1) { if (errno != EEXIST) { *p = '/'; return -1; } } *p = '/'; } return 0; }

Note that file_pathis modified during the action but gets restored afterwards. Therefore file_pathis not strictly const.

请注意,file_path它在操作期间被修改,但之后会恢复。所以file_path不严格const

回答by troglobit

Here's another take on mkpath(), using recursion, which is both small and readable. It makes use of strdupa()to avoid altering the given dirstring argument directly and to avoid using malloc()& free(). Make sure to compile with -D_GNU_SOURCEto activate strdupa()... meaning this code only works on GLIBC, EGLIBC, uClibc, and other GLIBC compatible C libraries.

这是另一个mkpath()使用递归的方法,它既小又可读。它利用strdupa()避免dir直接改变给定的字符串参数并避免使用malloc()& free()。确保编译-D_GNU_SOURCE以激活strdupa()...意味着此代码仅适用于 GLIBC、EGLIBC、uClibc 和其他与 GLIBC 兼容的 C 库。

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

After input both here and from Valery Frolov, in the Inadyn project, the following revised version of mkpath()has now been pushed to libite

在此处和来自 Valery Frolov 的输入后,在 Inadyn 项目中,以下修订版mkpath()现已推送到libite

/* 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 = '
char dirpath[80] = "/path/to/some/directory";
sprintf(mkcmd, "mkdir -p %s", dirpath);
system(mkcmd);
'; 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; }

It uses one more syscall, but otoh the code is more readable now.

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

回答by Chinmay Kanchi

Take a look at the bash source code here, and specifically look in examples/loadables/mkdir.c especially lines 136-210. If you don't want to do that, here's some of the source that deals with this (taken straight from the tar.gz that I've linked):

这里查看 bash 源代码,特别是查看 examples/loadables/mkdir.c 尤其是第 136-210 行。如果你不想这样做,这里有一些处理这个问题的来源(直接取自我链接的 tar.gz):

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

You can probably get away with a less general implementation.

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

回答by SiegeX

Apparently not, my two suggestions are:

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

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

Or if you don't want to use system()try looking at the coreutils mkdirsource code and see how they implemented the -poption.

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

回答by Kamiccolo

My recursive way of doing this:

我这样做的递归方式:

// 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] = '
#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;
}
'; 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; }

EDIT: fixed my old code snippet, bug-report by Namchester

编辑:修复了我的旧代码片段,Namchester 的错误报告

回答by DataGreed

Actually you can just use:

其实你可以只使用:

##代码##

回答by Daniel Griscom

I'm not allowed to comment on the first (and accepted) answer (not enough rep), so I'll post my comments as code in a new answer. The code below is based on the first answer, but fixes a number of problems:

我不允许对第一个(并被接受的)答案(没有足够的代表)发表评论,因此我将在新答案中将我的评论作为代码发布。下面的代码基于第一个答案,但解决了许多问题:

  • If called with a zero-length path, this does not read or write the character before the beginning of array opath[](yes, "why would you call it that way?", but on the other hand "why would you not fix the vulnerability?")
  • the size of opathis now PATH_MAX(which isn't perfect, but is better than a constant)
  • if the path is as long as or longer than sizeof(opath)then it is properly terminated when copied (which strncpy()doesn't do)
  • you can specify the mode of the written directory, just as you can with the standard mkdir()(although if you specify non-user-writeable or non-user-executable then the recursion won't work)
  • main() returns the (required?) int
  • removed a few unnecessary #includes
  • I like the function name better ;)
  • 如果使用零长度路径调用,则不会读取或写入数组开头之前的字符opath[](是的,“为什么要这样称呼它?”,但另一方面“为什么不修复漏洞? ”)
  • opath现在的大小PATH_MAX(这并不完美,但比常量好)
  • 如果路径与该路径一样长或更长,sizeof(opath)则在复制时正确终止(strncpy()不会这样做)
  • 您可以指定写入目录的模式,就像使用标准一样mkdir()(尽管如果您指定非用户可写或非用户可执行,则递归将不起作用)
  • main() 返回(必需的?)int
  • 删除了一些不必要的#includes
  • 我更喜欢函数名;)
##代码##

回答by Andreas Wehrmann

Here's my shot at a more general solution:

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

##代码##