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

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

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

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

этот код отлично работает, пока есть только один подкаталог (новый, как "dir1"), однако, когда есть путь к каталогу, такой как ("dir1 / dir2"), он терпит неудачу с ошибка: ENOENT, нет такого файла или каталога

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

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

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

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

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

13 ответов


один из вариантов-использовать модуль shelljs

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

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

страницы:

возможные варианты:

p: полный путь (при необходимости создаст промежуточные dirs)

Как отмечали другие, есть и другие более сфокусированные модули. Но, за пределами mkdirp, у него есть тонны других полезных операций оболочки(например, grep и т. д...) и он работает на windows и *nix


создать каталоги рекурсивно, если они не существуют! (ноль зависимостей)

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 && targetDir === curDir) {
        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, pass {isRelativeToScript: true}.
  • используя path.sep и path.resolve() не только / конкатенация, чтобы избежать кросс-платформенные вопросы.
  • используя fs.mkdirSync и обработка ошибки с try/catch если брошено для обработки условий гонки: другой процесс может добавить файл между вызовами в fs.existsSync() и fs.mkdirSync() и вызывает исключение.
    • другим способом достижения этого может быть проверка, существует ли файл, а затем его создание, т. е. if (!fs.existsSync(curDir) fs.mkdirSync(curDir);. Но это анти-шаблон, который оставляет код уязвимым для условий расы. Благодаря @GershomMaes комментарий о проверке существования каталога.
  • требует узел v6 и новее для поддержки деструктурируется. (Если у вас есть проблемы с реализацией этого решения со старыми версиями узлов, просто оставьте мне комментарий)

более надежный ответ-использовать use 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'....

fs-extra добавляет методы файловой системы, которые не включены в собственный модуль fs. Это капля в замене для fs.

установить fs-extra

$ npm install --save fs-extra

var 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


используя 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((currentPath, folder) => {
   currentPath += folder + path.sep;
   if (!fs.existsSync(currentPath)){
     fs.mkdirSync(currentPath);
   }
   return currentPath;
 }, '');

асинхронный способ рекурсивного создания каталогов:

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!')
})

Как насчет такого подхода :

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

это работает для относительного пути.


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

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 vs Linux, просто замените прямую косую черту двойной обратной косой чертой " \ " в обоих случаях выше, но TBH мы говорим о узле fs не командной строке windows, и первый довольно Прощающий, и приведенный выше код будет просто работать на Windows и является более полным решением межплатформенный.


вот моя императивная версия 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);
        }
    }
}

на основе Мунир это нулевые зависимости ответ, вот немного более новичок дружественный 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);
}

Exec может быть грязным в windows. Существует более "Ноди" решение. В принципе, у вас есть рекурсивный вызов, чтобы увидеть, существует ли каталог и погрузиться в дочерний (если он существует) или создать его. Вот функция, которая создаст детей и вызовет функцию по завершении:

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

}


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

использование модуля mkdir-p - хороший вариант, как и некоторые из состояний ответов. Но я сделал функцию js для своего приложения вместо того, чтобы использовать ее. Он работает для меня в среде windows; не тестировался в mac / linux. Если вы уже используете jQuery, то это будет удобно, я думаю. Или вы можете использовать эту логику и с чистым javascript.

        var arrPath=loc.split(path.sep);
        var tmpPath="";
        $.each(arrPath,(index,value)=>{             
            if(index==0 && value==""){
                /*in mac path starts with path seperator 'forward slash'
                then after the split 0th element will be empty string*/
                tmpPath=path.sep;
            }
            else{
                if(index==0){
                    tmpPath=value;
                }
                else{       
                    tmpPath=path.join(tmpPath,value);
                }

                if (!fs.existsSync(tmpPath)) { /* if the directory not exists*/
                   fs.mkdirSync(tmpPath);
                }
            }
        });

        if(fs.existsSync(tmpPath))
            return true;
        else
            return false