Jak utworzyć pełną ścieżkę za pomocą narzędzia fs.mkdirSync w węźle?


159

Próbuję utworzyć pełną ścieżkę, jeśli nie istnieje.

Kod wygląda następująco:

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

Ten kod działa świetnie, o ile istnieje tylko jeden podkatalog (newDest, taki jak „dir1”), jednak gdy istnieje ścieżka do katalogu, taka jak („dir1 / dir2”), kończy się niepowodzeniem z błędem: ENOENT, brak takiego pliku lub katalogu

Chciałbym móc utworzyć pełną ścieżkę z jak najmniejszą liczbą wierszy kodu.

Czytałem, że na fs jest opcja rekurencyjna i wypróbowałem to w ten sposób

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

Wydaje mi się, że rekurencyjne tworzenie katalogu, który nie istnieje, powinno być takie proste. Czy coś mi brakuje, czy muszę przeanalizować ścieżkę i sprawdzić każdy katalog i utworzyć go, jeśli jeszcze nie istnieje?

Jestem całkiem nowy w Node. Może używam starej wersji FS?


1
github.com/substack/node-mkdirp i wszelkiego rodzaju inne rozwiązania w tej wyszukiwarce Google .
jfriend00

4
@AndyRay To pytanie StackOverflow jest teraz najwyższym wynikiem w Google dla tego pytania, co jest zabawne, ponieważ oznacza to, że jest powtarzane ...
Matt Parkins

1
To był problem w starszych wersjach Node, aktualizacja do Node 12+ rozwiązuje problem
MrJomp

Odpowiedzi:


48

Jedną z opcji jest użycie modułu shelljs

npm zainstaluj shelljs

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

Z tej strony:

Dostępne opcje:

p: pełna ścieżka (w razie potrzeby utworzy katalogi pośrednie)

Jak zauważyli inni, istnieją inne, bardziej ukierunkowane moduły. Ale poza mkdirp ma mnóstwo innych użytecznych operacji powłoki (takich jak which, grep itp ...) i działa w systemie Windows i * nix


2
Dzięki! Skończyło się na tym, że użyłem exec (już tego używałem) i działało to jak marzenie. var exec = require ('child_process'). exec; var command = "mkdir -p '" + newDest + "'"; var options = {}; var after = function (error, stdout, stderr) {console.log ('błąd', błąd); console.log ('stdout', stdout); console.log ('stderr', stderr); } exec (polecenie, opcje, po);
David Silva Smith

24
Ta opcja może się zepsuć na platformach node.js, które nie mają instancji mkdir wiersza poleceń (tj. Hosty inne niż Linux-y), więc nie jest przenośna, jeśli ma to znaczenie.
cshotton,

1
@cshotton - odnosisz się do komentarza czy odpowiedzi? shelljs działa nawet w systemie Windows. exec mkdir -p (komentarz) oczywiście nie.
bryanmac

Możesz użyć tej fajnej funkcji z obietnicą lub wybranym oddzwonieniem.
Илья Зеленько

1
to nie jest rozwiązanie, to jest alternatywa dla rozwiązania. kontekst: pics.onsizzle.com/…
Nika Kasradze

412

Edytować

Wersja NodeJS 10.12.0dodała natywną obsługę obu mkdiri mkdirSynctworzenie katalogu rekurencyjnego z następującą recursive: trueopcją:

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

A jeśli wolisz fs Promises API, możesz pisać

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

Oryginalna odpowiedź

Twórz katalogi rekurencyjnie, jeśli nie istnieją! ( Zero zależności )

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

Stosowanie

// 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');

Próbny

Spróbuj!

Wyjaśnienia

  • [UPDATE] Błędy Ta platforma uchwyty rozwiązanie specyficzne jak EISDIRdla Mac i EPERMi EACCESdla Windows. Dziękuję wszystkim komentarzom zgłaszającym @PediT., @JohnQ, @ deed02392, @robyoder i @Almenon.
  • To rozwiązanie obsługuje zarówno ścieżki względne, jak i bezwzględne . Dzięki komentarzowi @john.
  • W przypadku ścieżek względnych katalogi docelowe zostaną utworzone (rozwiązane) w bieżącym katalogu roboczym. Aby rozwiązać je w odniesieniu do bieżącego katalogu skryptowego, pass {isRelativeToScript: true}.
  • Używanie path.sepi path.resolve(), a nie tylko /konkatenacja, w celu uniknięcia problemów między platformami.
  • Używanie fs.mkdirSynci obsługa błędu za pomocą try/catchif zgłoszonego w celu obsługi warunków wyścigu: inny proces może dodać plik między wywołaniami do fs.existsSync()i fs.mkdirSync()i powoduje wyjątek.
    • Innym sposobem na osiągnięcie tego może być sprawdzenie, czy plik istnieje, a następnie utworzenie go, tj if (!fs.existsSync(curDir) fs.mkdirSync(curDir);. Ale jest to anty-wzorzec, który pozostawia kod podatny na warunki wyścigu. Podziękowania dla @GershomMaes za komentarz dotyczący sprawdzenia istnienia katalogu.
  • Wymaga Node v6 i nowszych do obsługi destrukturyzacji. (Jeśli masz problemy z wdrożeniem tego rozwiązania w starszych wersjach Node, po prostu zostaw komentarz)

7
Głosuj za prostą, rekurencyjną odpowiedzią, która nie wymaga dodatkowej biblioteki ani podejścia!
MikingTheViking

1
Brakujące wymagania: const fs = require ('fs'); const path = require ('ścieżka');
Christopher Bull,

1
@ChristopherBull, celowo nie dodano tylko po to, aby skupić się na logice, ale w każdym razie dodałem je. Dzięki;)
Mouneer

1
12 wierszy ciągłego kodu, zero zależności, wezmę to za każdym razem.
moodboom

1
@Mouneer w systemie Mac OS X 10.12.6, błąd zgłaszany podczas próby utworzenia „/” po przekazaniu ścieżki bezwzględnej to „EISDIR” (Błąd: EISDIR: niedozwolona operacja na katalogu, mkdir '/'). Myślę, że prawdopodobnie sprawdzenie istnienia katalogu jest nadal najlepszym rozwiązaniem na wielu platformach (przyznając, że będzie wolniejsze).
John Q

78

Bardziej niezawodną odpowiedzią jest użycie mkdirp .

var mkdirp = require('mkdirp');

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

Następnie kontynuuj zapisywanie pliku w pełnej ścieżce za pomocą:

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

Wolę tę odpowiedź, ponieważ importujesz tylko to, czego potrzebujesz, a nie całą bibliotekę
Juan Mendes

1
Gratulacje z okazji odznaki populistów ;-)
janos

1
Dzięki. To najlepsza metoda.
Stepan Rafael


48

fs-extra dodaje metody systemu plików, które nie są zawarte w natywnym module fs. To kropla w zastępstwie fs.

zainstalować fs-extra

$ npm install --save fs-extra

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

Dostępne są opcje synchronizacji i asynchronizacji.

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


5
To najlepsza odpowiedź! Większość z nas i tak ma już fs-extra w aplikacji.
pagep

Byłoby wspaniale, gdyby oferował możliwość wykorzystania memfsdo testów jednostkowych. To nie ma :-( github.com/jprichardson/node-fs-extra/issues/274
schnatterer

31

Korzystając z redukuj, możemy sprawdzić, czy każda ścieżka istnieje i stworzyć ją w razie potrzeby, również w ten sposób myślę, że jest łatwiejsza do naśladowania. Edytowane, dzięki @Arvin, powinniśmy użyć path.sep, aby uzyskać właściwy separator segmentów ścieżki specyficzny dla platformy.

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
Udzielając odpowiedzi, najlepiej jest wyjaśnić, DLACZEGO twoja odpowiedź jest tą jedyną.
Stephen Rauch

Przepraszam, masz rację, myślę, że w ten sposób jest czystszy i łatwiejszy do naśladowania
josebui

4
@josebui Myślę, że lepiej jest używać „path.sep” zamiast ukośnika (/), aby uniknąć problemów specyficznych dla środowiska.
Arvin,

dobre rozwiązanie, ponieważ nie wymaga węzła> = 10, jak inne odpowiedzi
Karim

29

Ta funkcja została dodana do node.js w wersji 10.12.0, więc jest to tak proste, jak przekazanie opcji {recursive: true}jako drugiego argumentu do fs.mkdir()wywołania. Zobacz przykład w oficjalnych dokumentach .

Nie ma potrzeby stosowania zewnętrznych modułów ani własnej implementacji.


1
Znalazłem powiązane żądanie ściągnięcia github.com/nodejs/node/pull/23313
nurettin

1
Wystąpi błąd, gdy katalog istnieje i zostanie zatrzymany. Użycie bloku try catch może sprawić, że będzie on nadal tworzył inny nieistniejący folder.
Choco Li

1
To powinna być akceptowana odpowiedź. Nie rzuca, jeśli katalog już istnieje i może być używany z async / await przez fs.promises.mkdir.
Rich Apodaca

7

Wiem, że to stare pytanie, ale nodejs v10.12.0 obsługuje to teraz natywnie z recursiveopcją ustawioną na 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

Przykład dla systemu Windows (bez dodatkowych zależności i obsługi błędów)

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

Możesz po prostu rekurencyjnie sprawdzić, czy folder istnieje lub nie ma w ścieżce, i sprawić, by folder nie był obecny. ( BRAK BIBLIOTEKI ZEWNĘTRZNEJ )

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

Możesz użyć następnej funkcji

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

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

  fs.mkdirSync(fullPath)

  return fullPath
}

Więc co to robi:

  1. Utwórz pathszmienną, w której przechowuje każdą ścieżkę samodzielnie jako element tablicy.
  2. Dodaje „/” na końcu każdego elementu tablicy.
  3. Marki na cykl:
    1. Tworzy katalog z konkatenacji elementów tablicy, których indeksy są od 0 do bieżącej iteracji. Zasadniczo jest rekurencyjny.

Mam nadzieję, że to pomoże!

Nawiasem mówiąc, w Node 10.12.0 można użyć rekurencyjnego tworzenia ścieżek, podając je jako dodatkowy argument.

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

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


1

Za dużo odpowiedzi, ale oto rozwiązanie bez rekurencji, które działa poprzez podzielenie ścieżki, a następnie ponowne utworzenie jej od lewej do prawej

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

Dla tych, którzy martwią się kompatybilnością z Windowsem i Linuksem, po prostu zamień ukośnik na podwójny lewy ukośnik `` \ '' w obu powyższych wystąpieniach, ale TBH mówimy o węźle fs, a nie linii poleceń Windows, a ta pierwsza jest dość wybaczająca, a powyższy kod po prostu zadziała Windows i jest bardziej kompletnym rozwiązaniem dla wielu platform.


pliki w systemie Windows są obsługiwane za pomocą odwrotnego ukośnika, a nie ukośnika. Twój kod po prostu tam nie zadziała. C: \ data \ test ...
DDD,

Edytowano, ale sugeruję zatwierdzenie komentarza. W węźle wypróbuj poniższe rozwiązania i zobacz, co się stanie var fs = require ('fs') fs.mkdirSync ('test') fs.mkdirSync ('test \\ test1') fs.mkdirSync ('test / test2')
Hamiora

Cokolwiek mówisz… mój głos nie jest ważny, dopóki nie nauczysz się pisać lepszego kodu.
DDD

Ha ha. Ok, będę ciężko pracował, aby nauczyć się pisać lepszy kod. Przy okazji, większość odpowiedzi powyżej, w tym OP, używa ukośników. Zaproponuj zaprzestanie trollowania.
Hamiora

1
path.sepprzechodzi jako albo / lub \\ dla mnie. path.delimiterjest lub ;.
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

Asynchroniczny sposób rekurencyjnego tworzenia katalogów:

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

Oto moja imperatywna wersja mkdirpdla 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

A co z tym podejściem:

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

Działa to dla ścieżki względnej.


0

W oparciu o odpowiedź Mouneera o zerowych zależnościach, oto nieco bardziej przyjazny dla początkujących Typescriptwariant, jako moduł:

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

Tak czysty jak ten :)

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

Miałem problemy z rekurencyjną opcją fs.mkdir, więc utworzyłem funkcję, która wykonuje następujące czynności:

  1. Tworzy listę wszystkich katalogów, zaczynając od ostatecznego katalogu docelowego i kończąc na katalogu głównym.
  2. Tworzy nową listę potrzebnych katalogów do działania funkcji mkdir
  3. Sprawia, że ​​każdy katalog jest potrzebny, w tym plik końcowy

    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 może powodować bałagan w Windows. Jest bardziej "nodie" rozwiązanie. Zasadniczo masz wywołanie rekurencyjne, aby sprawdzić, czy katalog istnieje i zagłębić się w potomek (jeśli istnieje) lub go utworzyć. Oto funkcja, która utworzy dzieci i wywoła funkcję po zakończeniu:

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

Ta wersja działa lepiej w systemie Windows niż najlepsza odpowiedź, ponieważ rozumie oba, /a path.sepukośniki działają w systemie Windows tak, jak powinny. Obsługuje ścieżki bezwzględne i względne (względem 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;
}

Czy głos przeciwny za obsługę systemu Windows był prawidłowy? Czy wspomniałem, że działa również w innych systemach operacyjnych?
Qwertie,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.