Jak utworzyć katalog, jeśli nie istnieje za pomocą Node.js?

653

Czy to właściwy sposób na utworzenie katalogu, jeśli nie istnieje. Powinien mieć pełne pozwolenie na skrypt i być czytelny dla innych.

var dir = __dirname + '/upload';
if (!path.existsSync(dir)) {
    fs.mkdirSync(dir, 0744);
}
Whisher
źródło
1
Możliwy duplikat Node.js utwórz folder lub użyj istniejącego
Benny Neugebauer

Odpowiedzi:

1275
var fs = require('fs');
var dir = './tmp';

if (!fs.existsSync(dir)){
    fs.mkdirSync(dir);
}
chovy
źródło
28
Jeśli wykonujesz tę operację przy uruchamianiu lub inicjowaniu aplikacji, możesz zablokować wykonywanie, ponieważ zrobiłbyś to samo, gdybyś zrobił to asynchronicznie. Jeśli tworzysz katalog jako operację cykliczną, to jego zła praktyka, ale prawdopodobnie nie spowoduje żadnych problemów z wydajnością, ale mimo wszystko jest to zły nawyk. Używaj tylko do uruchamiania aplikacji lub innych jednorazowych operacji.
tsturzl
20
existSync () nie jest przestarzałe, istnieje is () - nodejs.org/api/fs.html#fs_fs_existssync_path
Ian Chadwick
używanie * Syncmetod jest zwykle nie-nie: nie chcę blokować pętli zdarzeń
Max Heiber
14
Używanie metod synchronizacji jest odpowiednie dla lokalnych skryptów i oczywiście nie jest to dobry pomysł na serwer.
Molo
Jeśli ten blok jest otoczony przez setTimeout, jest asynchroniczny .....................
Bryan Grace
185

Nie, z wielu powodów.

  1. pathModuł nie ma exists/ existsSyncmetody. Jest w fsmodule. (Być może właśnie napisałeś literówkę w swoim pytaniu?)

  2. Dokumenty wyraźnie zniechęcają do korzystania exists.

    fs.exists()jest anachronizmem i istnieje tylko z powodów historycznych. Prawie nigdy nie powinno być powodu, aby używać go we własnym kodzie.

    W szczególności sprawdzenie, czy plik istnieje przed otwarciem, jest anty-wzorcem, który naraża cię na warunki wyścigu: inny proces może usunąć plik między wywołaniami do fs.exists()i fs.open(). Wystarczy otworzyć plik i obsłużyć błąd, gdy go nie ma.

    Ponieważ mówimy raczej o katalogu niż pliku, ta rada sugeruje, że powinieneś bezwarunkowo zadzwonić mkdiri zignorować EEXIST.

  3. Ogólnie powinieneś unikać * Syncmetod. Blokują, co oznacza, że ​​absolutnie nic innego w twoim programie nie może się zdarzyć podczas przechodzenia na dysk. Jest to bardzo kosztowna operacja, a czas potrzebny na złamanie podstawowego założenia pętli zdarzeń węzła.

    SyncMetody * są zwykle dobre w szybkich skryptach jednofunkcyjnych (tych, które wykonują jedną rzecz, a następnie wychodzą), ale prawie nigdy nie powinny być używane podczas pisania serwera: serwer nie będzie w stanie odpowiedzieć nikomu przez cały czas trwania żądań We / Wy. Jeśli wiele żądań klientów wymaga operacji we / wy, serwer bardzo szybko się zatrzyma.


    Jedynym momentem, w którym rozważę użycie * Syncmetod w aplikacji serwera, jest operacja, która dzieje się raz (i tylko raz) przy uruchomieniu. Na przykład require faktycznie używareadFileSync do ładowania modułów.

    Nawet wtedy musisz zachować ostrożność, ponieważ wiele synchronicznych operacji we / wy może niepotrzebnie spowalniać czas uruchamiania serwera.


    Zamiast tego należy użyć asynchronicznych metod we / wy.

Jeśli więc zbierzemy te porady, otrzymamy coś takiego:

function ensureExists(path, mask, cb) {
    if (typeof mask == 'function') { // allow the `mask` parameter to be optional
        cb = mask;
        mask = 0777;
    }
    fs.mkdir(path, mask, function(err) {
        if (err) {
            if (err.code == 'EEXIST') cb(null); // ignore the error if the folder already exists
            else cb(err); // something else went wrong
        } else cb(null); // successfully created folder
    });
}

I możemy użyć tego w następujący sposób:

ensureExists(__dirname + '/upload', 0744, function(err) {
    if (err) // handle folder creation error
    else // we're all good
});

Oczywiście nie uwzględnia to przypadków typu edge

  • Co się stanie, jeśli folder zostanie usunięty podczas działania programu? (zakładając, że sprawdzasz, czy istnieje tylko raz podczas uruchamiania)
  • Co się stanie, jeśli folder już istnieje, ale ma niewłaściwe uprawnienia?
josh3736
źródło
1
Czy istnieje sposób na uniknięcie błędu SyntaxError: Literał ósemkowy nie jest dozwolony w trybie ścisłym?
Whisher,
8
Napisz to jako ułamek dziesiętny. 0744 == 484.
josh3736
3
Alternatywą jest użycie modułu, który rozszerza FS, aby mieć taką funkcjonalność, np. Github.com/jprichardson/node-fs-extra
Bret
czy ta flaga „maski” nadal jest istotna w 2019 r.? jaki był tego cel?
oldboy
Jest to tryb pliku unix - uprawnienia katalogu do odczytu / zapisu.
josh3736
33

mkdirMetoda ma zdolność do rekursywnie utworzyć żadnych katalogów w ścieżce, które nie istnieją, i ignorować te, które wykonują.

Z dokumentów Node v10 / 11 :

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

UWAGA: Musisz zaimportować wbudowany fs Najpierw moduł.

Oto trochę bardziej solidny przykład, który wykorzystuje natywne moduły ES (z włączoną flagą i rozszerzeniem .mjs), obsługuje ścieżki inne niż root i uwzględnia pełne nazwy ścieżek:

import fs from 'fs';
import path from 'path';

createDirectories(pathname) {
   const __dirname = path.resolve();
   pathname = pathname.replace(/^\.*\/|\/?[^\/]+\.[a-z]+|\/$/g, ''); // Remove leading directory markers, and remove ending /file-name.extension
   fs.mkdir(path.resolve(__dirname, pathname), { recursive: true }, e => {
       if (e) {
           console.error(e);
       } else {
           console.log('Success');
       }
    });
}

Możesz go używać jak createDirectories('/components/widget/widget.js');.

I oczywiście, prawdopodobnie chciałbyś zyskać więcej fantazji, używając obietnic z asynchronizacją / czekaj, aby wykorzystać tworzenie plików w bardziej czytelny, synchroniczny sposób podczas tworzenia katalogów; ale to wykracza poza zakres pytania.

nieco mniej
źródło
1
Dlaczego const __dirname = path.resolve (); i nie używać wbudowanego __dirname?
TamusJRoyce
29

Na wszelki wypadek zainteresowany wersją jednoliniową. :)

//or in typescript: import * as fs from 'fs';
const fs = require('fs');
!fs.existsSync(dir) && fs.mkdirSync(dir);
LeOn - Han Li
źródło
Rzekomy 1-liniowy, a nie 1-liniowy.
Hybrid web dev
20

Możesz po prostu użyć mkdiri złapać błąd, jeśli folder istnieje.
Jest to asynchroniczne (więc najlepsza praktyka) i bezpieczne.

fs.mkdir('/path', err => { 
    if (err && err.code != 'EEXIST') throw 'up'
    .. safely do your stuff here  
    })

(Opcjonalnie dodaj drugi argument do trybu).


Inne przemyślenia:

  1. Możesz wtedy użyć lub poczekać, używając natywnego promisify .

    const util = require('util'), fs = require('fs');
    const mkdir = util.promisify(fs.mkdir);
    var myFunc = () => { ..do something.. } 
    
    mkdir('/path')
        .then(myFunc)
        .catch(err => { if (err.code != 'EEXIST') throw err; myFunc() })
  2. Możesz stworzyć własną metodę obietnicy, taką jak (niesprawdzona):

    let mkdirAsync = (path, mode) => new Promise(
       (resolve, reject) => mkdir (path, mode, 
          err => (err && err.code !== 'EEXIST') ? reject(err) : resolve()
          )
       )
  3. Do sprawdzania synchronicznego możesz użyć:

    fs.existsSync(path) || fs.mkdirSync(path)
  4. Możesz też skorzystać z biblioteki, z której dwie są najbardziej popularne

SamGoody
źródło
1
w przypadku obiecującego podejścia nr 1 możesz ponownie ustawić haczyk. mkdir('/path').catch(err => { if (err.code != 'EEXIST') throw err;}).then(myFunc);
Co byłoby fajne
I użyj !==zamiast!=
Quentin Roy
18

Dzięki pakietowi fs-extra możesz to zrobić za pomocą jednej linijki :

const fs = require('fs-extra');

const dir = '/tmp/this/path/does/not/exist';
fs.ensureDirSync(dir);
galki
źródło
Taka niedoceniana odpowiedź! fs-extra ma dla mnie konieczność. Myślę, że pisanie ponad 10 linii tylko w celu sprawdzenia, czy folder istnieje, jest
aberracją
10

Najlepszym rozwiązaniem byłoby użycie modułu npm o nazwie node-fs-extra . Ma metodę o nazwie, mkdirktóra tworzy wspomniany katalog. Jeśli podasz długą ścieżkę do katalogu, automatycznie utworzy ona foldery nadrzędne. Moduł jest super zestawem modułu npm fs, więc możesz korzystać ze wszystkich funkcji, fstakże po dodaniu tego modułu.

Abdul Vajid
źródło
6
var dir = 'path/to/dir';
try {
  fs.mkdirSync(dir);
} catch(e) {
  if (e.code != 'EEXIST') throw e;
}
Ping.Goblue
źródło
4
W przypadku Node.js w wersji 7.4.0 dokumentacja stwierdza, że fs.exists()jest przestarzała, ale fs.existsSync()nie jest. Czy możesz dodać link do zasobu informującego, że fs.existsSync()jest amortyzowany?
francis
1
Odpowiedzi zawierające tylko kod nie są zbyt pomocne dla użytkowników, którzy w przyszłości odpowiedzą na to pytanie. Edytuj swoją odpowiedź, aby wyjaśnić, dlaczego kod rozwiązuje pierwotny problem
yivi
3
@francis, hmm, patrzyłem na Node.js v5, nodejs.org/docs/latest-v5.x/api/fs.html#fs_fs_existssync_path
Ping.Goblue 31.01.17
1
Dzięki! Wygląda na to, że funkcja istniała w wersji 0.12, przestarzała w wersji 4 i 5 i została przywrócona w wersji 6 i 7 ... Rodzaj funkcji zombi ...
Franciszek
1
Tak, najwyraźniej NIE jest już przestarzałe od dnia Apr 2018: nodejs.org/api/fs.html#fs_fs_existssync_path
LeOn - Han Li
5
    var filessystem = require('fs');
    var dir = './path/subpath/';

    if (!filessystem.existsSync(dir)){
        filessystem.mkdirSync(dir);
    }else
    {
        console.log("Directory already exist");
    }

To może ci pomóc :)

Wisznu S. Babu
źródło
5

ENOENT: brak takiego pliku lub katalogu

Rozwiązanie

const fs = require('fs')  // in javascript
import * as fs from "fs"  // in typescript
import fs from "fs"       // in typescript

// it will create the directory if it does not exist.
!fs.existsSync(`./assets/`) && fs.mkdirSync(`./assets/`, { recursive: true })
WasiF
źródło
1
to działa, dziękuję
Aljohn Yamaro
3

Chciałbym dodać refaktor Typescript Promise do odpowiedzi josh3736 .

Robi to samo i ma te same przypadki brzegowe, po prostu używa Obietnic, maszynopisów maszynopisowych i działa z „use strict”.

// https://en.wikipedia.org/wiki/File_system_permissions#Numeric_notation
const allRWEPermissions = parseInt("0777", 8);

function ensureFilePathExists(path: string, mask: number = allRWEPermissions): Promise<void> {
    return new Promise<void>(
        function(resolve: (value?: void | PromiseLike<void>) => void,
            reject: (reason?: any) => void): void{
            mkdir(path, mask, function(err: NodeJS.ErrnoException): void {
                if (err) {
                    if (err.code === "EEXIST") {
                        resolve(null); // ignore the error if the folder already exists
                    } else {
                        reject(err); // something else went wrong
                    }
                } else {
                    resolve(null); // successfully created folder
                }
            });
    });
}
Nathan Cooper
źródło
3

W przypadku węzła 10 + ES6:

import path from 'path';
import fs from 'fs';

(async () => {
  const dir = path.join(__dirname, 'upload');

  try {
    await fs.promises.mkdir(dir);
  } catch (error) {
    if (error.code === 'EEXIST') {
      // Something already exists, but is it a file or directory?
      const lstat = await fs.promises.lstat(dir);

      if (!lstat.isDirectory()) {
        throw error;
      }
    } else {
      throw error;
    }
  }
})();
sdgfsdh
źródło
2

Możesz użyć węzła File System komendy fs.stat, aby sprawdzić, czy katalog istnieje, i fs.mkdir, aby utworzyć katalog z wywołaniem zwrotnym, lub fs.mkdirSync, aby utworzyć katalog bez wywołania zwrotnego, jak w tym przykładzie:

//first require fs
const fs = require('fs');

// Create directory if not exist (function)
const createDir = (path) => {
    // check if dir exist
    fs.stat(path, (err, stats) => {
        if (stats.isDirectory()) {
            // do nothing
        } else {
            // if the given path is not a directory, create a directory
            fs.mkdirSync(path);
        }
    });
};
majid jiji
źródło
1

Oto mała funkcja rekurencyjnego tworzenia katalogów:

const createDir = (dir) => {
  // This will create a dir given a path such as './folder/subfolder' 
  const splitPath = dir.split('/');
  splitPath.reduce((path, subPath) => {
    let currentPath;
    if(subPath != '.'){
      currentPath = path + '/' + subPath;
      if (!fs.existsSync(currentPath)){
        fs.mkdirSync(currentPath);
      }
    }
    else{
      currentPath = subPath;
    }
    return currentPath
  }, '')
}
MrBlenny
źródło
0

Za pomocą asynchronizacji / oczekiwania:

const mkdirP = async (directory) => {
  try {
    return await fs.mkdirAsync(directory);
  } catch (error) {
    if (error.code != 'EEXIST') {
      throw e;
    }
  }
};

Musisz obiecać fs:

import nodeFs from 'fs';
import bluebird from 'bluebird';

const fs = bluebird.promisifyAll(nodeFs);
sdgfsdh
źródło