Как создать полный путь с узлом fs.mkdirSync?

159

Я пытаюсь создать полный путь, если он не существует.

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

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

Этот код прекрасно работает, пока существует только один подкаталог (newDest, например, 'dir1'), однако при наличии пути к каталогу ('dir1 / dir2') он завершается с ошибкой: ENOENT, такого файла или каталога нет

Я хотел бы иметь возможность создать полный путь с минимальным количеством строк кода.

Я читал, что есть рекурсивный вариант на фс и попробовал вот так

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

Я чувствую, что рекурсивно создать каталог, который не существует, настолько просто. Я что-то пропустил или мне нужно проанализировать путь, проверить каждый каталог и создать его, если он еще не существует?

Я довольно новичок в Node. Может я использую старую версию ФС?

Дэвид Сильва Смит
источник
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

bryanmac
источник
2
Спасибо! Я закончил тем, что использовал exec (я уже использовал это), и это работало как очарование. var exec = require ('child_process'). exec; var command = "mkdir -p '" + newDest + "'"; var options = {}; var after = function (error, stdout, stderr) {console.log ('error', error); 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
это не решение, это альтернатива решению. context: pics.onsizzle.com/…
Ника Касрадзе
413

редактировать

Версия 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');

демонстрация

Попытайся!

Пояснения

  • [ОБНОВЛЕНИЕ] Это решение обрабатывает специфичные EISDIRдля платформы ошибки, как для Mac, так EPERMи EACCESдля Windows. Благодаря всем отчетным комментариям @PediT., @JohnQ, @ deed02392, @robyoder и @Almenon.
  • Это решение обрабатывает как относительные, так и абсолютные пути. Благодаря комментарию @john.
  • В случае относительных путей целевые каталоги будут созданы (разрешены) в текущем рабочем каталоге. Чтобы разрешить их относительно текущего сценария dir, передайте {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, просто оставьте мне комментарий)
Mouneer
источник
7
Upvote для простого, рекурсивного ответа, не требующего дополнительной библиотеки или подхода!
MikingTheViking
1
Отсутствующие операторы require: const fs = require ('fs'); const path = require ('path');
Кристофер Бул
1
@ChristopherBull, намеренно не добавлен, чтобы просто сосредоточиться на логике, но в любом случае, я добавил их. Спасибо;)
Mouneer
1
12 строк сплошного кода, ноль зависимостей, я буду брать его каждый раз.
настроение
1
@Mouneer в Mac OS X 10.12.6, ошибка при попытке создать «/» после передачи по абсолютному пути - «EISDIR» (ошибка: EISDIR: недопустимая операция с каталогом, mkdir '/'). Я думаю, что, вероятно, проверка существования dir все еще является лучшим кроссплатформенным способом (признавая, что это будет медленнее).
Джон 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'....
cshotton
источник
Предпочитаю этот ответ, поскольку вы импортируете только то, что вам нужно, а не целую библиотеку
Хуан Мендес
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

Deejers
источник
5
Это лучший ответ! У большинства из нас уже есть fs-extra в приложении.
pagep
Это было бы замечательно, если бы предлагалось использовать его memfsдля модульного тестирования. Это не так :-( github.com/jprichardson/node-fs-extra/issues/274
schnatterer
31

Используя Reduce, мы можем проверить, существует ли каждый путь, и создать его, если это необходимо, и таким образом, я думаю, легче следовать. Отредактировано, спасибо @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
4
@josebui Я думаю, что лучше использовать «path.sep» вместо косой черты (/), чтобы избежать проблем со средой.
Арвин
хорошее решение, потому что не требует узла> = 10, как и другие ответы
Карим
29

Эта функция была добавлена ​​в node.js в версии 10.12.0, так что это так же просто, как передать параметр в {recursive: true}качестве второго аргумента fs.mkdir()вызова. Смотрите пример в официальных документах .

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

КАПАХ
источник
1
Я нашел соответствующий запрос на получение данных github.com/nodejs/node/pull/23313
nurettin
1
Он выдаст ошибку, когда каталог существует и остановится. Использование блока try catch может заставить его создавать другие несуществующие папки.
Чоко Ли
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;
});
Нельсон Овало
источник
6

Теперь с NodeJS> = 10.12.0вы можете использовать fs.mkdirSync(path, { recursive: true }) fs.mkdirSync

Уильям Пенагос
источник
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 = путь.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 и более полное решение кроссплатформенное.


файлы на окнах обрабатываются с обратной косой чертой, а не с прямой. Ваш код просто не будет работать там. C: \ data \ test ...
DDD

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

Что бы вы ни говорили .., мой отрицательный голос останется, пока вы не научитесь писать лучший код.
DDD

Ха - ха. Хорошо, я буду очень усердно работать над обучением написанию лучшего кода. Кстати, большинство ответов выше, включая OP, используют косую черту. Предлагаю вам прекратить троллинг.
Хамиора

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;
}
Qwertie
источник
Был ли понижен голос за правильную поддержку Windows? Я упоминал, что это работает и на других ОС?
Qwertie