Come creare un percorso completo con fs.mkdirSync del nodo?


159

Sto cercando di creare un percorso completo se non esiste.

Il codice è simile al seguente:

var fs = require('fs');
if (!fs.existsSync(newDest)) fs.mkdirSync(newDest); 

Questo codice funziona perfettamente fintanto che esiste una sola sottodirectory (una newDest come 'dir1') tuttavia quando c'è un percorso di directory come ('dir1 / dir2') fallisce con Errore: ENOENT, nessun file o directory

Mi piacerebbe essere in grado di creare il percorso completo con tutte le righe di codice necessarie.

Ho letto che esiste un'opzione ricorsiva su fs e l'ho provata in questo modo

var fs = require('fs');
if (!fs.existsSync(newDest)) fs.mkdirSync(newDest,'0777', true);

Sento che dovrebbe essere così semplice creare ricorsivamente una directory che non esiste. Mi sto perdendo qualcosa o devo analizzare il percorso e controllare ogni directory e crearla se non esiste già?

Sono abbastanza nuovo per Node. Forse sto usando una vecchia versione di FS?



4
@AndyRay Questa domanda StackOverflow è ora il miglior risultato in google per questa domanda, il che è divertente perché ciò significa che è ricorsi ....
Matt Parkins

1
Questo era un problema con le versioni precedenti di Node, l'aggiornamento a Nodo 12+ risolve il problema
MrJomp

Risposte:


48

Un'opzione è utilizzare il modulo shelljs

npm installa shelljs

var shell = require('shelljs');
shell.mkdir('-p', fullPath);

Da quella pagina:

Opzioni disponibili:

p: percorso completo (se necessario creerà directory intermedie)

Come altri hanno notato, ci sono altri moduli più mirati. Ma, al di fuori di mkdirp, ha tonnellate di altre utili operazioni shell (come quali, grep ecc ...) e funziona su windows e * nix


2
Grazie! Ho finito per usare exec (lo stavo già usando) e ha funzionato come un incantesimo. var exec = require ('child_process'). exec; var command = "mkdir -p '" + newDest + "'"; opzioni var = {}; var after = function (errore, stdout, stderr) {console.log ('errore', errore); console.log ('stdout', stdout); console.log ('stderr', stderr); } exec (comando, opzioni, dopo);
David Silva Smith,

24
Questa opzione potrebbe non funzionare su piattaforme node.js che non dispongono di un'istanza mkdir della riga di comando (ovvero host non Linux-y), quindi non è portatile, se questo è importante.
cshotton,

1
@cshotton - ti riferisci al commento o alla risposta? shelljs funziona anche su Windows. exec mkdir -p (il commento) ovviamente no.
bryanmac,

Puoi usare questa fantastica funzione con Promise o callback di tua scelta.
Илья Зеленько,

1
questa non è una soluzione, questa è un'alternativa alla soluzione. contesto: pics.onsizzle.com/…
Nika Kasradze,

413

modificare

La versione di NodeJS 10.12.0ha aggiunto un supporto nativo per entrambi mkdire mkdirSyncper creare una directory ricorsivamente con l' recursive: trueopzione come la seguente:

fs.mkdirSync(targetDir, { recursive: true });

E se preferisci fs Promises API, puoi scrivere

fs.promises.mkdir(targetDir, { recursive: true });

Risposta originale

Crea directory in modo ricorsivo se non esistono! ( Dipendenze zero )

const fs = require('fs');
const path = require('path');

function mkDirByPathSync(targetDir, { isRelativeToScript = false } = {}) {
  const sep = path.sep;
  const initDir = path.isAbsolute(targetDir) ? sep : '';
  const baseDir = isRelativeToScript ? __dirname : '.';

  return targetDir.split(sep).reduce((parentDir, childDir) => {
    const curDir = path.resolve(baseDir, parentDir, childDir);
    try {
      fs.mkdirSync(curDir);
    } catch (err) {
      if (err.code === 'EEXIST') { // curDir already exists!
        return curDir;
      }

      // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows.
      if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure.
        throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`);
      }

      const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1;
      if (!caughtErr || caughtErr && curDir === path.resolve(targetDir)) {
        throw err; // Throw if it's just the last created dir.
      }
    }

    return curDir;
  }, initDir);
}

uso

// Default, make directories relative to current working directory.
mkDirByPathSync('path/to/dir');

// Make directories relative to the current script.
mkDirByPathSync('path/to/dir', {isRelativeToScript: true});

// Make directories with an absolute path.
mkDirByPathSync('/path/to/dir');

dimostrazione

Provalo!

spiegazioni

  • [UPDATE] errori Questa soluzione maniglie piattaforma-specifici, come EISDIRper Mac e EPERMe EACCESper Windows. Grazie a tutti i commenti sui rapporti di @PediT., @JohnQ, @ deed02392, @robyoder e @Almenon.
  • Questa soluzione gestisce sia percorsi relativi che assoluti . Grazie al commento di @john.
  • Nel caso di percorsi relativi, le directory di destinazione verranno create (risolte) nella directory di lavoro corrente. Per risolverli in relazione alla directory dello script corrente, passa {isRelativeToScript: true}.
  • Utilizzo path.sepe path.resolve(), non solo /concatenazione, per evitare problemi multipiattaforma.
  • Utilizzando fs.mkdirSyncspedizione l'errore con try/catchse gettati per gestire le condizioni di gara: altro processo può aggiungere il file tra le chiamate a fs.existsSync()ed fs.mkdirSync()e provoca un'eccezione.
    • L'altro modo per raggiungere questo obiettivo potrebbe essere verificare l'esistenza di un file e quindi crearlo, vale a dire if (!fs.existsSync(curDir) fs.mkdirSync(curDir);. Ma questo è un anti-pattern che rende il codice vulnerabile alle condizioni di gara. Grazie al commento di @GershomMaes sul controllo dell'esistenza della directory.
  • Richiede Nodo v6 e successivi per supportare la destrutturazione. (Se riscontri problemi nell'implementazione di questa soluzione con le versioni precedenti del nodo, lasciami un commento)

7
Migliora la risposta semplice e ricorsiva che non richiede una libreria o un approccio aggiuntivi!
MikingTheViking

1
Dichiarazioni mancanti mancanti: const fs = require ('fs'); const path = require ('percorso');
Christopher Bull,

1
@ChristopherBull, intenzionalmente non aggiunto solo per concentrarsi sulla logica, ma comunque, li ho aggiunti. Grazie;)
Mouneer

1
12 righe di codice solido, zero dipendenze, lo prenderò ogni volta.
moodboom

1
@Mouneer su Mac OS X 10.12.6, l'errore generato quando si tenta di creare "/" dopo aver passato un percorso assoluto è "EISDIR" (Errore: EISDIR: operazione illegale su una directory, mkdir '/'). Penso che probabilmente verificare l'esistenza di dir sia ancora il miglior modo multipiattaforma (riconoscendo che sarà più lento).
John Q,

78

Una risposta più efficace è usare mkdirp .

var mkdirp = require('mkdirp');

mkdirp('/path/to/dir', function (err) {
    if (err) console.error(err)
    else console.log('dir created')
});

Quindi procedere a scrivere il file nel percorso completo con:

fs.writeFile ('/path/to/dir/file.dat'....

Preferisci questa risposta poiché stai importando proprio ciò di cui hai bisogno, non un'intera libreria
Juan Mendes,

1
Congratulazioni per il badge populista ;-)
Janos

1
Grazie. È il metodo migliore.
Stepan Rafael,


48

fs-extra aggiunge i metodi del file system che non sono inclusi nel modulo fs nativo. È un calo in sostituzione di fs.

Installare fs-extra

$ npm install --save fs-extra

const fs = require("fs-extra");
// Make sure the output directory is there.
fs.ensureDirSync(newDest);

Ci sono opzioni di sincronizzazione e asincrono.

https://github.com/jprichardson/node-fs-extra/blob/master/docs/ensureDir.md


5
Questa è la risposta migliore! La maggior parte di noi ha già l'app fs-extra nell'app.
pagep

Sarebbe fantastico se offrisse la possibilità di utilizzare memfsper i test unitari. Non :-( github.com/jprichardson/node-fs-extra/issues/274
schnatterer

31

Utilizzando riduciamo possiamo verificare se esiste ogni percorso e crearlo se necessario, anche in questo modo penso che sia più facile da seguire. A cura di, grazie a @Arvin, dovremmo usare path.sep per ottenere il separatore di segmenti di percorso specifico per la piattaforma.

const path = require('path');

// Path separators could change depending on the platform
const pathToCreate = 'path/to/dir'; 
pathToCreate
 .split(path.sep)
 .reduce((prevPath, folder) => {
   const currentPath = path.join(prevPath, folder, path.sep);
   if (!fs.existsSync(currentPath)){
     fs.mkdirSync(currentPath);
   }
   return currentPath;
 }, '');

4
Quando dai una risposta è preferibile dare una spiegazione del PERCHÉ la tua risposta è quella.
Stephen Rauch,

Scusa, hai ragione, penso che in questo modo sia più pulito e più facile da seguire
josebui

4
@josebui Penso che sia meglio usare "path.sep" invece di barra (/) per evitare problemi specifici dell'ambiente.
Arvin,

buona soluzione perché non richiede nodo> = 10 come le altre risposte
Karim

29

Questa funzione è stata aggiunta a node.js nella versione 10.12.0, quindi è facile come passare un'opzione {recursive: true}come secondo argomento alla fs.mkdir()chiamata. Vedi l' esempio nei documenti ufficiali .

Non sono necessari moduli esterni o la tua implementazione.


1
Ho trovato la relativa richiesta pull github.com/nodejs/node/pull/23313
nurettin

1
Verrà generato un errore quando la directory esiste e si arresterà. Utilizzare un blocco catch catch può farlo continuare a creare altre cartelle inesistenti.
Choco Li,

1
Questa dovrebbe essere la risposta accettata. Non genera se la directory esiste già e può essere utilizzata con async / wait tramite fs.promises.mkdir.
Rich Apodaca,

7

so che questa è una vecchia domanda, ma nodejs v10.12.0 ora supporta questo nativamente con l' recursiveopzione impostata su true. fs.mkdir

// Creates /tmp/a/apple, regardless of whether `/tmp` and /tmp/a exist.
fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => {
  if (err) throw err;
});


2

Esempio per Windows (nessuna dipendenza aggiuntiva e gestione degli errori)

const path = require('path');
const fs = require('fs');

let dir = "C:\\temp\\dir1\\dir2\\dir3";

function createDirRecursively(dir) {
    if (!fs.existsSync(dir)) {        
        createDirRecursively(path.join(dir, ".."));
        fs.mkdirSync(dir);
    }
}

createDirRecursively(dir); //creates dir1\dir2\dir3 in C:\temp

2

Puoi semplicemente controllare che la cartella esista o meno nel percorso in modo ricorsivo e creare la cartella mentre controlli se non sono presenti. ( NESSUNA BIBLIOTECA ESTERNA )

function checkAndCreateDestinationPath (fileDestination) {
    const dirPath = fileDestination.split('/');
    dirPath.forEach((element, index) => {
        if(!fs.existsSync(dirPath.slice(0, index + 1).join('/'))){
            fs.mkdirSync(dirPath.slice(0, index + 1).join('/')); 
        }
    });
}

2

È possibile utilizzare la funzione successiva

const recursiveUpload = (path: string) => {const percorsi = path.split ("/")

const fullPath = paths.reduce((accumulator, current) => {
  fs.mkdirSync(accumulator)
  return `${accumulator}/${current}`
  })

  fs.mkdirSync(fullPath)

  return fullPath
}

Quindi cosa fa:

  1. Crea una pathsvariabile, dove memorizza ogni percorso da solo come elemento dell'array.
  2. Aggiunge "/" alla fine di ogni elemento dell'array.
  3. Fa per il ciclo:
    1. Crea una directory dalla concatenazione di elementi dell'array quali indici vanno da 0 all'iterazione corrente. Fondamentalmente, è ricorsivo.

Spero che aiuti!

A proposito, in Nodo v10.12.0 è possibile utilizzare la creazione di percorsi ricorsivi dandogli come argomento aggiuntivo.

fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => { if (err) throw err; });

https://nodejs.org/api/fs.html#fs_fs_mkdirsync_path_options


1

Troppe risposte, ma ecco una soluzione senza ricorsione che funziona dividendo il percorso e poi da sinistra a destra per ricostruirlo di nuovo

function mkdirRecursiveSync(path) {
    let paths = path.split(path.delimiter);
    let fullPath = '';
    paths.forEach((path) => {

        if (fullPath === '') {
            fullPath = path;
        } else {
            fullPath = fullPath + '/' + path;
        }

        if (!fs.existsSync(fullPath)) {
            fs.mkdirSync(fullPath);
        }
    });
};

Per chi si preoccupa della compatibilità tra Windows e Linux, è sufficiente sostituire la barra rovesciata con una doppia barra rovesciata '\' in entrambe le occorrenze sopra, ma TBH stiamo parlando di nodo fs non della riga di comando di Windows e il primo è abbastanza tollerante e il codice sopra semplicemente funzionerà semplicemente Windows ed è più una soluzione completa multipiattaforma.


i file su Windows sono gestiti con barra rovesciata e non barra rovesciata. Il tuo codice semplicemente non funzionerà lì. C: \ data \ test ...
DDD

Modificato ma ti suggerisco di convalidare il tuo commento. Sul nodo provare quanto segue e vedere cosa succede var fs = request ('fs') fs.mkdirSync ('test') fs.mkdirSync ('test \\ test1') fs.mkdirSync ('test / test2')
Hamiora

Qualunque cosa tu stia dicendo ..., il mio voto negativo rimane ancora finché non impari a scrivere un codice migliore.
DDD,

Haha. Ok, lavorerò sodo per imparare a scrivere codice migliore. A proposito, la maggior parte delle risposte di cui sopra, incluso l'OP, usa barre rovesciate. Ti suggerisco di smettere di pescare a traina.
Hamiora,

1
path.sepsta arrivando come / o \\ per me. path.delimiterè: o;.
Josh Anderson Slate

1
const fs = require('fs');

try {
    fs.mkdirSync(path, { recursive: true });
} catch (error) {
    // this make script keep running, even when folder already exist
    console.log(error);
}

0

Un modo asincrono per creare ricorsivamente directory:

import fs from 'fs'

const mkdirRecursive = function(path, callback) {
  let controlledPaths = []
  let paths = path.split(
    '/' // Put each path in an array
  ).filter(
    p => p != '.' // Skip root path indicator (.)
  ).reduce((memo, item) => {
    // Previous item prepended to each item so we preserve realpaths
    const prevItem = memo.length > 0 ? memo.join('/').replace(/\.\//g, '')+'/' : ''
    controlledPaths.push('./'+prevItem+item)
    return [...memo, './'+prevItem+item]
  }, []).map(dir => {
    fs.mkdir(dir, err => {
      if (err && err.code != 'EEXIST') throw err
      // Delete created directory (or skipped) from controlledPath
      controlledPaths.splice(controlledPaths.indexOf(dir), 1)
      if (controlledPaths.length === 0) {
        return callback()
      }
    })
  })
}

// Usage
mkdirRecursive('./photos/recent', () => {
  console.log('Directories created succesfully!')
})

0

Ecco la mia versione imperativa di mkdirpper nodejs.

function mkdirSyncP(location) {
    let normalizedPath = path.normalize(location);
    let parsedPathObj = path.parse(normalizedPath);
    let curDir = parsedPathObj.root;
    let folders = parsedPathObj.dir.split(path.sep);
    folders.push(parsedPathObj.base);
    for(let part of folders) {
        curDir = path.join(curDir, part);
        if (!fs.existsSync(curDir)) {
            fs.mkdirSync(curDir);
        }
    }
}

0

Che ne dici di questo approccio:

if (!fs.existsSync(pathToFile)) {
            var dirName = "";
            var filePathSplit = pathToFile.split('/');
            for (var index = 0; index < filePathSplit.length; index++) {
                dirName += filePathSplit[index]+'/';
                if (!fs.existsSync(dirName))
                    fs.mkdirSync(dirName);
            }
        }

Questo funziona per il percorso relativo.


0

Sulla base della risposta a zero dipendenze di mouneer , ecco una Typescriptvariante leggermente più adatta ai principianti , come modulo:

import * as fs from 'fs';
import * as path from 'path';

/**
* Recursively creates directories until `targetDir` is valid.
* @param targetDir target directory path to be created recursively.
* @param isRelative is the provided `targetDir` a relative path?
*/
export function mkdirRecursiveSync(targetDir: string, isRelative = false) {
    const sep = path.sep;
    const initDir = path.isAbsolute(targetDir) ? sep : '';
    const baseDir = isRelative ? __dirname : '.';

    targetDir.split(sep).reduce((prevDirPath, dirToCreate) => {
        const curDirPathToCreate = path.resolve(baseDir, prevDirPath, dirToCreate);
        try {
            fs.mkdirSync(curDirPathToCreate);
        } catch (err) {
            if (err.code !== 'EEXIST') {
                throw err;
            }
            // caught EEXIST error if curDirPathToCreate already existed (not a problem for us).
        }

        return curDirPathToCreate; // becomes prevDirPath on next call to reduce
    }, initDir);
}

0

Pulito come questo :)

function makedir(fullpath) {
  let destination_split = fullpath.replace('/', '\\').split('\\')
  let path_builder = destination_split[0]
  $.each(destination_split, function (i, path_segment) {
    if (i < 1) return true
    path_builder += '\\' + path_segment
    if (!fs.existsSync(path_builder)) {
      fs.mkdirSync(path_builder)
    }
  })
}

0

Ho avuto problemi con l'opzione ricorsiva di fs.mkdir, quindi ho creato una funzione che procede come segue:

  1. Crea un elenco di tutte le directory, a partire dalla directory di destinazione finale e fino al genitore principale.
  2. Crea un nuovo elenco di directory necessarie per il funzionamento della funzione mkdir
  3. Rende necessaria ogni directory, incluso il finale

    function createDirectoryIfNotExistsRecursive(dirname) {
        return new Promise((resolve, reject) => {
           const fs = require('fs');
    
           var slash = '/';
    
           // backward slashes for windows
           if(require('os').platform() === 'win32') {
              slash = '\\';
           }
           // initialize directories with final directory
           var directories_backwards = [dirname];
           var minimize_dir = dirname;
           while (minimize_dir = minimize_dir.substring(0, minimize_dir.lastIndexOf(slash))) {
              directories_backwards.push(minimize_dir);
           }
    
           var directories_needed = [];
    
           //stop on first directory found
           for(const d in directories_backwards) {
              if(!(fs.existsSync(directories_backwards[d]))) {
                 directories_needed.push(directories_backwards[d]);
              } else {
                 break;
              }
           }
    
           //no directories missing
           if(!directories_needed.length) {
              return resolve();
           }
    
           // make all directories in ascending order
           var directories_forwards = directories_needed.reverse();
    
           for(const d in directories_forwards) {
              fs.mkdirSync(directories_forwards[d]);
           }
    
           return resolve();
        });
     }

-1

Exec può essere disordinato su Windows. C'è una soluzione più "nodie". Fondamentalmente, hai una chiamata ricorsiva per vedere se esiste una directory e tuffarti nel bambino (se esiste) o crearla. Ecco una funzione che creerà i bambini e chiamerà una funzione al termine:

fs = require('fs');
makedirs = function(path, func) {
 var pth = path.replace(/['\\]+/g, '/');
 var els = pth.split('/');
 var all = "";
 (function insertOne() {
   var el = els.splice(0, 1)[0];
   if (!fs.existsSync(all + el)) {
    fs.mkdirSync(all + el);
   }
   all += el + "/";
   if (els.length == 0) {
    func();
   } else {
     insertOne();
   }
   })();

}


-1

Questa versione funziona meglio su Windows rispetto alla risposta principale perché comprende entrambi /e path.sepquindi le barre in avanti funzionano su Windows come dovrebbero. Supporta percorsi assoluti e relativi (relativi al process.cwd).

/**
 * Creates a folder and if necessary, parent folders also. Returns true
 * if any folders were created. Understands both '/' and path.sep as 
 * path separators. Doesn't try to create folders that already exist,
 * which could cause a permissions error. Gracefully handles the race 
 * condition if two processes are creating a folder. Throws on error.
 * @param targetDir Name of folder to create
 */
export function mkdirSyncRecursive(targetDir) {
  if (!fs.existsSync(targetDir)) {
    for (var i = targetDir.length-2; i >= 0; i--) {
      if (targetDir.charAt(i) == '/' || targetDir.charAt(i) == path.sep) {
        mkdirSyncRecursive(targetDir.slice(0, i));
        break;
      }
    }
    try {
      fs.mkdirSync(targetDir);
      return true;
    } catch (err) {
      if (err.code !== 'EEXIST') throw err;
    }
  }
  return false;
}

Il downvote per supportare correttamente Windows? Ho già detto che funziona anche su altri sistemi operativi?
Qwertie,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.