如何使用节点的fs.mkdirSync创建完整路径?


159

我正在尝试创建完整路径(如果不存在)。

代码如下:

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

只要只有一个子目录(例如“ dir1”之类的newDest),此代码就可以很好地工作,但是当存在一个目录路径(“ 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上排名第一的结果,这很有趣,因为这意味着它是递归的....
Matt Parkins

1
那是旧版Node上的一个问题,更新到Node 12+可以解决问题
MrJomp

Answers:


48

一种选择是使用shelljs模块

npm安装shelljs

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

从该页面:

可用选项:

p:完整路径(如有必要,将创建中间目录)

正如其他人指出的那样,还有其他更专注的模块。但是,在mkdirp之外,它还有大量其他有用的shell操作(例如grep等),并且可以在Windows和* nix上运行


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(command,options,after);
David Silva Smith

24
在没有命令行mkdir实例的node.js平台(即非Linux-y主机)上,此选项可能会中断,因此,如果重要的话,它是不可移植的。
cshotton '16

1
@cshotton-您是指评论还是答案?shelljs甚至可以在Windows上使用。exec mkdir -p(注释)当然不是。
bryanmac

您可以将此酷函数与Promise或您选择的回调一起使用。
ИльяЗеленько

1
这不是解决方案,而是解决方案的替代方案。背景:pics.onsizzle.com/...
尼卡Kasradze

412

编辑

NodeJS版本10.12.0增加了对两者的本机支持,mkdirmkdirSync使用以下命令递归创建目录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 EPERMEACCES用于Windows。感谢@PediT。,@ JohnQ,@ deed02392,@ robyoder和@Almenon的所有报告意见。
  • 该解决方案同时处理相对绝对路径路径。感谢@john评论。
  • 对于相对路径,将在当前工作目录中创建(解析)目标目录。要相对于当前脚本目录解析它们,请传递{isRelativeToScript: true}
  • 使用path.seppath.resolve(),而不仅仅是/连接,以避免跨平台问题。
  • 使用fs.mkdirSync和处理try/catch如果抛出的错误以处理竞争条件:另一个进程可能会在对fs.existsSync()和的调用之间添加文件fs.mkdirSync()并导致异常。
    • 实现此目标的另一种方法是检查文件是否存在,然后创建它,即if (!fs.existsSync(curDir) fs.mkdirSync(curDir);。但这是一种反模式,使代码容易受到竞争条件的影响。感谢@GershomMaes评论目录是否存在。
  • 需要Node v6及更高版本才能支持解构。(如果您在使用旧版Node实施此解决方案时遇到问题,请给我留言)

7
支持简单,递归的响应,无需其他库或方法!
MikingTheViking

1
缺少require语句:const fs = require('fs'); const path = require('path');
Christopher Bull

1
@ChristopherBull,不是有意添加只是为了关注逻辑,而是无论如何,我添加了它们。谢谢;)
Mouneer

1
12行固定代码,零依赖关系,我每次都会接受。
moodboom

1
在Mac OS X 10.12.6上使用@Mouneer时,在传递绝对路径后尝试创建“ /”时抛出的错误是“ EISDIR”(错误:EISDIR:对目录mkdir'/'的非法操作)。我认为检查目录是否存在仍然是最好的跨平台方法(承认它会更慢)。
约翰Q

78

一个更可靠的答案是使用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'....

首选此答案,因为您只是导入所需的内容,而不是整个库的内容
Juan Mendes

1
祝贺民粹主义徽章;-)
janos

1
谢谢。这是最好的方法。
斯蒂芬·拉斐尔


48

fs-extra添加了本机fs模块中未包含的文件系统方法。这是对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-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;
 }, '');

4
给出答案时,最好对原因做出一些解释
斯蒂芬·劳奇

抱歉,您是对的,我认为这样更干净,更容易理解
josebui

4
@josebui我认为最好使用“ path.sep”而不是正斜杠(/)来避免特定于环境的问题。
Arvin

一个好的解决方案,因为不需要像其他答案一样节点> = 10
Karim

29

此功能已在10.12.0版中添加到node.js中,因此就像将选项{recursive: true}作为第二个参数传递给fs.mkdir()调用一样简单。请参阅官方文档中示例

无需外部模块或您自己的实现。


1
我找到了相关的请求请求github.com/nodejs/node/pull/23313
nurettin '18

1
当目录存在并停止时,它将引发错误。使用try catch块可以使其继续创建其他不存在的文件夹。
Choco Li

1
这应该是公认的答案。如果目录已经存在,则不会抛出该异常,并且可以通过fs.promises.mkdir与async / await一起使用。
Rich Apodaca

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 path = 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

编辑但建议您验证您的评论。在节点尝试以下,看看会发生什么变种FS =要求( 'FS')fs.mkdirSync( '测试')fs.mkdirSync( '测试\\ TEST1')fs.mkdirSync( '测试/测试2')
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在Windows上可能很凌乱。还有一个更多的“ nodie”解决方案。从根本上讲,您可以进行递归调用以查看目录是否存在,然后深入子目录(如果确实存在)或创建该目录。这是一个将创建子项并在完成时调用一个函数的函数:

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.