Як створити повний шлях за допомогою fs.mkdirSync вузла?


159

Я намагаюся створити повний шлях, якщо його не існує.

Код виглядає так:

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

Цей код працює чудово, доки є лише один підкаталог (новийDest, подібний до "dir1"), однак, коли є шлях до каталогу, як ("dir1 / dir2"), він не вдається з помилкою: ENOENT, немає такого файлу чи каталогу

Я хотів би мати можливість створити повний шлях з якомога меншою кількістю рядків коду.

Я прочитав, що є рекурсивний варіант на fs і спробував це так

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

Я відчуваю, що це повинно бути таким простим рекурсивно створювати каталог, який не існує. Я щось пропускаю чи мені потрібно проаналізувати шлях і перевірити кожен каталог і створити його, якщо він ще не існує?

Я досить новачок у Node. Можливо, я використовую стару версію FS?


1
github.com/substack/node-mkdirp та всілякі інші рішення в цьому пошуку Google .
jfriend00

4
@AndyRay Це запитання щодо StackOverflow тепер є найкращим результатом у Google для цього питання, що смішно, бо це означає, що це рекурси ....
Метт Паркінс,

1
Це було проблемою для старих версій Node, оновлення до Node 12+ вирішує проблему
MrJomp

Відповіді:


48

Один з варіантів - використовувати модуля shelljs

npm встановити shelljs

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

З цієї сторінки:

Доступні варіанти:

p: повний шлях (створить при необхідності проміжні грязи)

Як зазначають інші, існують і інші більш зосереджені модулі. Але поза mkdirp, він має багато інших корисних операцій з оболонками (наприклад, grep тощо), і він працює на windows і * nix


2
Дякую! Я закінчив використовувати exec (я вже користувався цим), і це спрацювало як шарм. var exec = вимагати ('child_process'). exec; var command = "mkdir -p '" + newDest + "'"; var options = {}; var after = функція (помилка, stdout, stderr) {console.log ('помилка', помилка); console.log ('stdout', stdout); console.log ('stderr', stderr); } exec (команда, параметри, після);
Девід Сільва Сміт

24
Ця опція може зламатися на платформах node.js, які не мають командного рядка mkdir екземпляр (тобто, не-Linux-y-хости), тому він не є портативним, якщо це має значення.
cshotton

1
@cshotton - ви посилаєтесь на коментар чи відповідь? shelljs працює навіть на windows. exec mkdir -p (коментар) звичайно ні.
bryanmac

Ви можете користуватися цією цікавою функцією за допомогою Promise або зворотного дзвінка на ваш вибір.
Илья Зеленько

1
це не рішення, це альтернатива рішенню. контекст: pics.onsizzle.com/…
Nika Kasradze

412

Редагувати

Версія NodeJS 10.12.0додала нативну підтримку для обох mkdirта mkdirSyncстворювати каталог рекурсивно з recursive: trueможливістю наступного:

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

І якщо ви віддаєте перевагу fs Promises API, можете писати

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

Оригінальний відповідь

Створюйте каталоги рекурсивно, якщо їх не існує! ( Нульові залежності )

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

Використання

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

Демо

Спробуй це!

Пояснення

  • [UPDATE] Це рішення обробляє помилки, пов’язані з платформою, як EISDIRдля Mac, так EPERMі EACCESдля Windows. Дякуємо всім коментарям доповідей від @PediT., @JohnQ, @ deed02392, @robyoder та @Almenon.
  • Це рішення обробляє як відносні, так і абсолютні шляхи. Завдяки коментарю @john
  • У випадку відносних шляхів цільові каталоги будуть створені (вирішені) у поточному робочому каталозі. Щоб вирішити їх відносно поточного режиму сценарію, перейдіть{isRelativeToScript: true} .
  • Використання path.sepта path.resolve(), а не просто /конкатенація, щоб уникнути проблем між платформами.
  • Використання fs.mkdirSyncі обробка помилок з try/catchякщо кинуто обробляти умови гонки: інший процес може додати файл між викликами fs.existsSync()і fs.mkdirSync()і викликає виняток.
    • Інший спосіб домогтися того, що можна було б перевірити , якщо файл існує , то його створення, тобто, if (!fs.existsSync(curDir) fs.mkdirSync(curDir);. Але це анти-шаблон, який залишає код вразливим до перегонів. Завдяки коментарю @GershomMaes про перевірку існування каталогу.
  • Потрібен Node v6 та новіших версій для підтримки руйнування. (Якщо у вас є проблеми з реалізацією цього рішення зі старими версіями Node, просто залиште мені коментар)

7
Оновлення для легкої, рекурсивної відповіді, яка не потребує додаткової бібліотеки чи підходу!
MikingTheViking

1
Відсутній оператор вимагають: const fs = requ ('fs'); const шлях = вимагати ('шлях');
Крістофер Булл

1
@ChristopherBull, навмисно не доданий лише для того, щоб зосередитись на логіці, але все одно я додав їх. Дякую;)
Mouneer

1
12 рядків суцільного коду, нульові залежності, я візьму його кожен раз.
moodboom

1
@Mouneer на Mac OS X 10.12.6, помилка, викинута при спробі створення "/" після проходження в абсолютному шляху, - "EISDIR" (Помилка: EISDIR: незаконна робота в каталозі, mkdir '/'). Я думаю, що, ймовірно, перевірка наявності режиму все-таки є найкращим крос-платформним способом (визнати, що це буде повільніше).
Джон Q

78

Більш надійною відповіддю є використання використання mkdirp .

var mkdirp = require('mkdirp');

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

Потім переходимо до запису файла в повний шлях за допомогою:

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

Віддайте перевагу цій відповіді, оскільки ви імпортуєте саме те, що вам потрібно, а не всю бібліотеку
Juan Mendes

1
Поздоровлення на знаку народника ;-)
janos

1
Дякую. Це далеко кращий метод.
Степан Рафаель


48

fs-extra додає методи файлової системи, які не включені в рідний модуль fs. Це падіння заміни на фс.

Встановити fs-extra

$ npm install --save fs-extra

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

Є параметри синхронізації та асинхронізації.

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


5
Це найкраща відповідь! У більшості з нас у будь-якому випадку вже є додатки fs у додатку.
pagep

Це було б чудово, якби це запропонувало можливість використовувати memfsдля тестування одиниць. Це не так :-( github.com/jprichardson/node-fs-extra/isissue/274
schnatterer

31

Використовуючи зменшення, ми можемо перевірити, чи існує кожен шлях, і створити його за потреби, також таким чином я думаю, що його простіше прослідкувати. Відредаговано, завдяки @Arvin, ми повинні використовувати path.sep, щоб отримати належний роздільник сегмента шляху для платформи.

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


Вибачте, ви маєте рацію, я думаю, що таким чином чистіше і простіше дотримуватися
josebui

4
@josebui Я думаю, що краще використовувати "path.sep", а не перекинутий косу риску (/), щоб уникнути специфічних проблем щодо навколишнього середовища.
Арвін

гарне рішення, оскільки не потрібен вузол> = 10, як інші відповіді
Карім

29

Ця версія була додана до node.js у версії 10.12.0, тому це так просто, як передавати параметр, {recursive: true}як другий аргумент для fs.mkdir()виклику. Дивіться приклад в офіційних документах .

Немає необхідності у зовнішніх модулях чи власній реалізації.


1
Я знайшов відповідний запит на притягнення github.com/nodejs/node/pull/23313
nurettin

1
Він видасть помилку, коли каталог існує і зупиниться. Використання блоку спробу вловлювання може змусити його створювати інші неіснуючі папки.
Choco Li

1
Це має бути прийнятою відповіддю. Він не викидає, якщо каталог вже існує, і його можна використовувати з async / await через fs.promises.mkdir.
Багатий Аподака

7

Я знаю, що це старе питання, але тепер nodejs v10.12.0 підтримує це споконвічно з recursiveможливістю встановити значення 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

Приклад для Windows (відсутність додаткових залежностей та обробка помилок)

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

Ви можете просто перевірити, чи існують папки в рекурсивному режимі чи немає, і зробити папку, коли ви перевіряєте, чи їх немає. ( НЕ БЕЗПЕЧНО БІБЛІОТЕКА )

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

Можна використовувати наступну функцію

const recursiveUpload = (шлях: рядок) => {const paths = path.split ("/")

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

  fs.mkdirSync(fullPath)

  return fullPath
}

Отже, що це робить:

  1. Створіть pathsзмінну, де вона зберігає кожен шлях сам по собі як елемент масиву.
  2. Додає "/" в кінці кожного елемента в масиві.
  3. Склад для циклу:
    1. Створює каталог з об'єднання елементів масиву, індексів яких становить від 0 до поточної ітерації. В основному, це рекурсивно.

Сподіваюся, що це допомагає!

До речі, в Node v10.12.0 ви можете використовувати рекурсивне створення шляху, надавши його в якості додаткового аргументу.

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

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


1

Забагато відповідей, але ось рішення без рекурсії, яке працює, розбиваючи шлях, а потім будуючи його знову зліва направо, створюйте резервну копію

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

Для тих, хто стурбований сумісністю Windows проти Linux, просто замініть нахил наперед на подвійний нахил "\" в обох випадках вище, але TBH ми говоримо про вузол fs, а не командний рядок Windows, і колишній досить прощає, і вищевказаний код просто працюватиме Windows і є більш повним рішенням крос-платформи.


Файли на Windows обробляються з косою косою рисою, а не вперед. Ваш код там просто не працюватиме. C: \ data \ test ...
DDD

Відредаговано, але пропоную перевірити коментар. На вузлі спробуйте наступне і подивіться, що відбувається var fs = requ ('fs') fs.mkdirSync ('test') fs.mkdirSync ('test \\ test1') fs.mkdirSync ('test / test2')
Hamiora

Що б ви не говорили .., мій голос вниз все ще залишається, поки ви не навчитеся писати кращий код.
DDD

Ха-ха. Гаразд, я дуже попрацюю над тим, щоб навчитися писати кращий код. BTW Більшість відповідей вище, включаючи ОП, використовують нахили вперед. Запропонуйте зупинити тролінг.
Гаміора

1
path.sepпереживає як / або \\ для мене. path.delimiterє: або;.
Джош Андерсон Шифер

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

Асинхронний спосіб створення каталогів рекурсивно:

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

Ось моя імперативна версія mkdirpдля 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

Як щодо цього підходу:

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

Це працює для відносного шляху.


0

Виходячи з відповіді нулевих залежностей Mouneer , ось трохи зручніший для початківців Typescriptваріант, як модуль:

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

Так чисто, як це :)

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

У мене виникли проблеми з рекурсивною опцією fs.mkdir, тому я зробив функцію, яка виконує наступні дії:

  1. Створює список усіх каталогів, починаючи з остаточного цільового режиму і працює до головного батьківського пристрою.
  2. Створює новий список необхідних каталогів для роботи функції mkdir
  3. Здійснює необхідність кожного каталогу, включаючи остаточний

    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 може бути безладним на вікнах. Є більш "ноді" рішення. По суті, у вас є рекурсивний дзвінок, щоб побачити, чи існує каталог, і зануритись у дитину (якщо вона існує) або створити її. Ось функція, яка створить дітей та викличе функцію, коли закінчите:

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

Ця версія працює в Windows краще, ніж найкраща відповідь, оскільки вона розуміє і те, /і path.sepте, що передні косої риси працюють у Windows як слід. Підтримує абсолютні та відносні шляхи (відносно 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;
}

Правильно було підтверджено підтримку Windows? Я вже згадував, що він працює і на інших ОС?
Qwertie
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.