Llamada recursiva al sistema mkdir() en Unix

9 minutos de lectura

avatar de usuario
alex marshall

Después de leer la página man de mkdir(2) para la llamada al sistema Unix con ese nombre, parece que la llamada no crea directorios intermedios en una ruta, solo el último directorio en la ruta. ¿Hay alguna forma (u otra función) de crear todos los directorios en la ruta sin tener que analizar manualmente la cadena de mi directorio y crear individualmente cada directorio?

avatar de usuario
carl norum

Desafortunadamente, no hay una llamada al sistema que lo haga por usted. Supongo que eso se debe a que no hay una forma de tener una semántica realmente bien definida para lo que debería suceder en los casos de error. ¿Debe dejar los directorios que ya se han creado? ¿Borra los? ¿Qué pasa si las eliminaciones fallan? Y así…

Sin embargo, es bastante fácil rodar el tuyo, y un rápido google para ‘mkdir recursivo‘ apareció una serie de soluciones. Aquí hay uno que estaba cerca de la parte superior:

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] == "https://stackoverflow.com/")
        tmp[len - 1] = 0;
    for (p = tmp + 1; *p; p++)
        if (*p == "https://stackoverflow.com/") {
            *p = 0;
            mkdir(tmp, S_IRWXU);
            *p = "https://stackoverflow.com/";
        }
    mkdir(tmp, S_IRWXU);
}

  • Lo único que cambiaría es tmp[256] a tmp[PATH_MAX] también #incluir

    – rouzier

    2 de marzo de 2015 a las 16:37


  • Versión mejorada: gist.github.com/JonathonReinhart/…

    – Jonathan Reinhart

    14 de mayo de 2016 a las 3:35

  • snprintf() devuelve la longitud de la cadena formateada, la llamada a strlen() es superfluo len = snprintf(tmp, ... . Puede verificar el desbordamiento del búfer de esa manera if(len >= sizeof tmp). Con el strlen() no es posible.

    – Patrick Schlüter

    23 de mayo de 2016 a las 7:41

  • ¿Qué sucede si mkdir en bucle no pudo crear un directorio? Por ejemplo de permisos? Recomiendo encarecidamente la versión mencionada de github gist.github.com/JonathonReinhart/…

    – iwtu

    12/09/2016 a las 13:36


  • @rouzier PATH_MAX puede no ser una mejora ya que PATH_MAX no se definirá en Sistemas compatibles con POSIX donde el valor varía entre diferentes sistemas de archivos (negrita mía): “Una definición de una de las constantes simbólicas en la siguiente lista se omitirá desde el <limits.h> encabezado en implementaciones específicas donde el valor correspondiente es igual o mayor que el mínimo establecido, pero donde el valor puede variar según el archivo al que se aplica”.

    –Andrew Henle

    20 de julio de 2021 a las 19:28


hmm pensé que mkdir -p hace eso?

mkdir -p esto/es/una/ruta/completa/de/cosas

  • Sí, lo hace, pero la pregunta se refiere a una llamada de función C.

    –Craig McQueen

    19 de febrero de 2013 a las 2:42

  • De hecho, los votos positivos presumiblemente reflejan que esta ha sido una respuesta útil para muchos, pero es una respuesta a una pregunta diferente a la que se hizo.

    – Chris Stratton

    7 oct 2014 a las 18:20


  • Sin embargo, se podría echar un vistazo al código fuente de mkdir para ver cómo eso lo hace. Haciendo un google rápido, parece que el código relevante está en mkancestdirs.c en coreutils

    – juego

    31 de marzo de 2015 a las 9:52


  • Esta es una respuesta para diferentes preguntas formuladas.

    – Nenad Radulovic

    24/10/2016 a las 12:57

  • @gamen, el enlace en tu comentario está obsoleto.

    – merlín2011

    15 de enero de 2017 a las 2:50

avatar de usuario
Yaroslav Stavnichiy

Aquí está mi solución. Al llamar a la función a continuación, se asegura de que existan todos los directorios que conducen a la ruta del archivo especificado. Tenga en cuenta que file_path el argumento no es el nombre del directorio aquí, sino una ruta a un archivo que va a crear después de llamar mkpath().

P.ej., mkpath("/home/me/dir/subdir/file.dat", 0755) creará /home/me/dir/subdir si no existe. mkpath("/home/me/dir/subdir/", 0755) hace lo mismo

También funciona con rutas relativas.

Devoluciones -1 y conjuntos errno en caso de error.

int mkpath(char* file_path, mode_t mode) {
    assert(file_path && *file_path);
    for (char* p = strchr(file_path + 1, "https://stackoverflow.com/"); p; p = strchr(p + 1, "https://stackoverflow.com/")) {
        *p = '\0';
        if (mkdir(file_path, mode) == -1) {
            if (errno != EEXIST) {
                *p = "https://stackoverflow.com/";
                return -1;
            }
        }
        *p = "https://stackoverflow.com/";
    }
    return 0;
}

Tenga en cuenta que file_path se modifica durante la acción pero se restaura después. Por lo tanto file_path no es estrictamente const.

  • mejor que la respuesta aceptada; con manejo de errores!

    – Chris Maes

    20 de febrero de 2015 a las 11:42

  • El único problema es que utiliza un no const char * como parámetro porque cambia el contenido del puntero original. Esto no es ideal, ya que no funcionará con cadenas constantes estáticas, por ejemplo, y tiene un requisito de API innecesario.

    – Felipe Tonello

    17 de junio de 2021 a las 3:23

  • @FelipeTonello Puede asignar memoria fácilmente y hacer una copia del parámetro como se sugiere en otras respuestas. Mi objetivo era el rendimiento, así que traté de evitar operaciones costosas como la asignación de memoria.

    – Yaroslav Stavnichiy

    18 de junio de 2021 a las 18:59

avatar de usuario
troglobit

Aquí hay otra versión mkpath(), utilizando la recursividad, que es pequeña y legible. Hace uso de strdupa() para evitar alterar lo dado dir argumento de cadena directamente y para evitar el uso malloc() & free(). Asegúrate de compilar con -D_GNU_SOURCE Activar strdupa() … lo que significa que este código solo funciona en GLIBC, EGLIBC, uClibc y otras bibliotecas C compatibles con GLIBC.

int mkpath(char *dir, mode_t mode)
{
    if (!dir) {
        errno = EINVAL;
        return 1;
    }

    if (strlen(dir) == 1 && dir[0] == "https://stackoverflow.com/")
        return 0;

    mkpath(dirname(strdupa(dir)), mode);

    return mkdir(dir, mode);
}

Después de los aportes tanto aquí como de Valery Frolov, en el proyecto Inadyn, la siguiente versión revisada de mkpath() ahora ha sido empujado a libérate

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

Utiliza una llamada al sistema más, pero ahora el código es más legible.

avatar de usuario
Chinmay Kanchi

Eche un vistazo al código fuente de bash aquí, y busque específicamente en ejemplos/cargables/mkdir.c, especialmente en las líneas 136-210. Si no desea hacer eso, aquí hay algunas de las fuentes que se ocupan de esto (tomadas directamente del tar.gz que he vinculado):

/* 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 == "https://stackoverflow.com/")
    p++;

  while (p = strchr (p, "https://stackoverflow.com/"))
  {
      *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++ = "https://stackoverflow.com/";   /* restore slash */
      while (*p == "https://stackoverflow.com/")
          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;
}

Probablemente pueda salirse con la suya con una implementación menos general.

Aparentemente no, mis dos sugerencias son:

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

O si no quieres usar system() intente mirar los coreutils mkdir código fuente y ver cómo implementaron el -p opción.

avatar de usuario
daniel griscom

No tengo permitido comentar sobre la primera (y aceptada) respuesta (no hay suficientes representantes), así que publicaré mis comentarios como código en una nueva respuesta. El siguiente código se basa en la primera respuesta, pero soluciona una serie de problemas:

  • Si se llama con una ruta de longitud cero, esto no lee ni escribe el carácter antes del comienzo de la matriz opath[] (sí, “¿por qué lo llamarías así?”, pero por otro lado “¿por qué no arreglarías la vulnerabilidad?”)
  • la talla de opath es ahora PATH_MAX (que no es perfecto, pero es mejor que una constante)
  • si el camino es tan largo o más largo que sizeof(opath) entonces se termina correctamente cuando se copia (que strncpy() no lo hace)
  • puede especificar el modo del directorio escrito, tal como puede hacerlo con el estándar mkdir() (aunque si especifica que no sea grabable por el usuario o no ejecutable por el usuario, la recursividad no funcionará)
  • main() devuelve el int (¿requerido?)
  • eliminó algunos innecesarios #includes
  • Me gusta más el nombre de la función 😉
// 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] == "https://stackoverflow.com/")
        opath[len - 1] = '\0';
    for(p = opath; *p; p++)
        if (*p == "https://stackoverflow.com/") {
            *p = '\0';
            if (access(opath, F_OK))
                mkdir(opath, mode);
            *p = "https://stackoverflow.com/";
        }
    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;
}

  • Cambiaría esto para devolver int. entonces es solo un cambio de 1 palabra para refactorizar.

    – jaybny

    8 de septiembre de 2016 a las 8:56

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad