Chiamata di sistema mkdir () ricorsiva su Unix

Dopo aver letto la pagina man mkdir (2) per la chiamata di sistema Unix con quel nome, sembra che la chiamata non crei directory intermedie in un percorso, solo l’ultima directory nel percorso. C’è un modo (o un’altra funzione) per creare tutte le directory nel percorso senza ricorrere all’analisi manuale della mia stringa di directory e alla creazione individuale di ogni directory?

Sfortunatamente, non c’è una chiamata di sistema a farlo per te. Immagino che non ci sia un modo per avere una semantica veramente ben definita per ciò che dovrebbe accadere nei casi di errore. Dovrebbe lasciare le directory che sono già state create? Cancellali? Cosa succede se le eliminazioni falliscono? E così via…

Tuttavia, è abbastanza semplice eseguire il rollover e un rapido google per ” mkdir ricorsivo ” ha trovato una serie di soluzioni. Ecco uno che era vicino alla cima:

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

hmm ho pensato che mkdir -p lo facesse?

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

Ecco la mia soluzione. Chiamando la funzione di seguito si garantisce che tutte le dir che portano al percorso del file specificato esistano. Nota che file_path argomento file_path non è il nome della directory qui, ma piuttosto un percorso per un file che stai per creare dopo aver chiamato mkpath() .

Ad esempio, mkpath("/home/me/dir/subdir/file.dat", 0755) crea /home/me/dir/subdir se non esiste. mkpath("/home/me/dir/subdir/", 0755) fa lo stesso.

Funziona anche con percorsi relativi.

Restituisce -1 e imposta errno in caso di errore.

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

Nota che file_path viene modificato durante l’azione ma viene ripristinato in seguito. Quindi file_path non è rigorosamente const .

Ecco un’altra versione di mkpath() , che usa la ricorsione, che è sia piccola che leggibile. Fa uso di strdupa() per evitare di alterare direttamente l’argomento della stringa dir fornita e di evitare l’uso di malloc() e free() . Assicurati di compilare con -D_GNU_SOURCE per triggersre strdupa() … il che significa che questo codice funziona solo su GLIBC, EGLIBC, uClibc e altre librerie C compatibili con GLIBC.

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

Dopo l’input sia qui che di Valery Frolov, nel progetto Inadyn, la seguente versione rivista di mkpath() è stata ora spinta alla libita

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

Usa un altro syscall, ma otoh il codice è più leggibile ora.

Dai un’occhiata al codice sorgente di bash qui , e guarda in particolare negli esempi / loadables / mkdir.c in particolare le linee 136-210. Se non vuoi farlo, ecco alcuni dei sorgenti che trattano questo (presi direttamente dal tar.gz che ho collegato):

 /* 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; } 

Probabilmente puoi cavarcanvas con un’implementazione meno generale.

Apparentemente no, i miei due suggerimenti sono:

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

O se non vuoi usare system() prova a guardare il codice sorgente di mkdir coreutils e vedi come hanno implementato l’opzione -p .

In realtà puoi semplicemente usare:

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

Non sono autorizzato a commentare la prima (e accettata) risposta (non abbastanza rep), quindi posterò i miei commenti come codice in una nuova risposta. Il codice seguente si basa sulla prima risposta, ma risolve un numero di problemi:

  • Se chiamato con un percorso di lunghezza zero, questo non legge né scrive il carattere prima dell’inizio di opath[] array opath[] (sì, “perché lo chiameresti in quel modo?”, Ma d’altra parte “perché non aggiusti? la vulnerabilità? “)
  • la dimensione di opath ora è PATH_MAX (che non è perfetta, ma è migliore di una costante)
  • se il percorso è lungo o più lungo di sizeof(opath) , viene terminato correttamente quando viene copiato (cosa che non fa strncpy() )
  • è ansible specificare la modalità della directory scritta, proprio come è ansible con lo standard mkdir() (sebbene se si specifica non eseguibile dall’utente o non eseguibile dall’utente, la ricorsione non funzionerà)
  • main () restituisce il (richiesto?) int
  • rimosso alcuni #include s non necessari
  • Mi piace il nome della funzione meglio;)
 // Based on http://nion.modprobe.de/blog/archives/357-Recursive-directory-creation.html #include  #include  #include  #include  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; } 

Il mio modo ricorsivo di fare questo:

 #include  /* Only POSIX version of dirname() */ #include  #include  #include  #include  #include  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; } 

EDIT: corretto il mio vecchio snippet di codice, bug-report di Namchester

Le altre due risposte fornite sono per mkdir(1) e non mkdir(2) come richiesto dall’utente, ma puoi guardare il codice sorgente per quel programma e vedere come implementa le opzioni -p che chiama ripetutamente mkdir(2) come necessario.

La mia soluzione:

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

Ecco il mio colpo a una soluzione più generale:

 #include  #include  #include  #include  #include  #include  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  [...]\n", argv[ 0 ] ); exit( 1 ); } for( i = 1; i < argc; i++ ) { rv = mkdir_with_leading( argv[ i ] ); if( 0 != rv ) return rv; } return 0; } 

Una soluzione molto semplice, basta inserire input: 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!!"); } 

Abbastanza dritto Questo può essere un buon punto di partenza

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

Si analizza questa funzione in un percorso completo più le autorizzazioni desiderate, ad esempio S_IRUSR , per un elenco completo delle modalità andare qui https://techoverflow.net/2013/04/05/how-to-use-mkdir-from-sysstat- h /

La stringa fullpath verrà divisa dal carattere “/” e le singole directory verranno aggiunte alla stringa aggrpaz una alla volta. Ogni iterazione del ciclo chiama la funzione mkdir, passandogli il percorso di aggregazione fino ad ora oltre alle autorizzazioni. Questo esempio può essere migliorato, non sto controllando l’output della funzione mkdir e questa funzione funziona solo con percorsi assoluti.