Рекурсивный системный вызов mkdir () в Unix - PullRequest
61 голосов
/ 25 февраля 2010

После прочтения справочной страницы mkdir (2) для системного вызова Unix с этим именем выясняется, что вызов не создает промежуточные каталоги в пути, а только последний каталог в пути. Есть ли способ (или другая функция) создать все каталоги в пути, не прибегая к ручному анализу строки моего каталога и индивидуальному созданию каждого каталога?

Ответы [ 14 ]

84 голосов
/ 25 февраля 2010

К сожалению, нет системного вызова, чтобы сделать это за вас. Я предполагаю, что это потому, что нет способа иметь действительно четко определенную семантику для того, что должно происходить в случаях ошибок. Должен ли он оставить каталоги, которые уже были созданы? Удалить их? Что делать, если удаление не удалось? И так далее ...

Однако довольно легко накатить свой собственный, и быстрый поиск Google для ' 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);
}
66 голосов
/ 11 июля 2012

хм, я думал, что mkdir -p это делает?

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

20 голосов
/ 09 февраля 2012

Вот мое решение. Вызывая функцию ниже, вы гарантируете, что все каталоги, ведущие к указанному пути к файлу, существуют. Обратите внимание, что аргумент 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.

11 голосов
/ 17 декабря 2013

Вот еще один вариант 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);
}

После ввода здесь и от Валерия Фролова, в проекте 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);
}

Он использует еще один системный вызов, но теперь код более читабелен.

9 голосов
/ 25 февраля 2010

Взгляните на исходный код 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;
}

Вероятно, вы можете избежать более общей реализации.

8 голосов
/ 25 февраля 2010

Очевидно нет, мои два предложения:

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

Или, если вы не хотите использовать system(), попробуйте просмотреть исходный код coreutils mkdir и посмотреть, как они реализовали опцию -p.

2 голосов
/ 15 сентября 2013

На самом деле вы можете просто использовать:

mkdir -p ./some/directories/to/be/created/
1 голос
/ 16 января 2015

Мне не разрешено комментировать первый (и принятый) ответ (недостаточно повторений), поэтому я буду публиковать свои комментарии в виде кода в новом ответе. Приведенный ниже код основан на первом ответе, но устраняет ряд проблем:

  • Если вызывается с путем нулевой длины, он не читает и не записывает символ перед началом массива opath[] (да, «почему вы бы назвали его таким образом?», Но с другой стороны «почему бы Вы не исправили уязвимость? ")
  • размер opath теперь равен PATH_MAX (что не идеально, но лучше, чем константа)
  • если путь длиннее или длиннее sizeof(opath), то при копировании он корректно завершается (чего не делает strncpy())
  • вы можете указать режим записанного каталога, так же, как вы можете использовать стандартный mkdir() (хотя, если вы укажете, что пользователь не может писать или выполнять его, рекурсия не будет работать)
  • main () возвращает (обязательно?) Int
  • удалено несколько ненужных #include с
  • мне больше нравится имя функции;)
// 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;
}
1 голос
/ 09 июля 2013

Мой рекурсивный способ сделать это:

#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

0 голосов
/ 10 апреля 2017

Довольно прямо. Это может быть хорошей отправной точкой

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, и эта функция работает только с абсолютными путями.

...