【问题标题】:How to create full path with node's fs.mkdirSync?如何使用节点的 fs.mkdirSync 创建完整路径?
【发布时间】:2015-10-17 04:54:32
【问题描述】:

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

代码如下:

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?

【问题讨论】:

  • github.com/substack/node-mkdirp 以及this Google search 上的各种其他解决方案。
  • @AndyRay 这个 StackOverflow 问题现在是谷歌这个问题的最高结果,这很有趣,因为这意味着它是递归的......
  • 这是旧版本 Node 的问题,更新到 Node 12+ 即可解决问题

标签: node.js fs


【解决方案1】:

更新

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

演示

Try It!

说明

  • [更新] 此解决方案可处理特定于平台的错误,例如 Mac 的 EISDIR 和 Windows 的 EPERMEACCES。感谢@PediT.、@JohnQ、@deed02392、@robyoder 和 @Almenon 的所有报告 cmets。
  • 此解决方案同时处理相对绝对路径。感谢@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 版本实施此解决方案时遇到问题,请给我留言)

【讨论】:

  • 赞成简单的递归响应,不需要额外的库或方法!
  • 缺少要求语句: const fs = require('fs'); const path = require('path');
  • @ChristopherBull,故意不添加只是为了专注于逻辑,但无论如何,我添加了它们。谢谢;)
  • 12行实心代码,零依赖,我每次都拿来。
  • @Mouneer on Mac OS X 10.12.6,在传入绝对路径后尝试创建“/”时抛出的错误是“EISDIR”(错误:EISDIR:对目录的非法操作,mkdir '/')。我认为可能检查 dir 是否存在仍然是最好的跨平台方式(承认它会更慢)。
【解决方案2】:

一种选择是使用shelljs module

npm 安装 shelljs

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

从那个页面:

可用选项:

p:完整路径(必要时会创建中间目录)

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

编辑:cmets 建议这不适用于没有 mkdir cli 实例的系统。事实并非如此。这就是 shelljs 的重点——创建一个可移植的跨平台 shell 类函数集。它甚至适用于 Windows。

【讨论】:

  • 谢谢!我最终使用了 exec (我已经在使用它了),它就像一个魅力。 var exec = require('child_process').exec; var command = "mkdir -p '" + newDest + "'";变种选项 = {}; var after = function(error, stdout, stderr) { console.log('error', error); console.log('stdout', 标准输出); console.log('stderr', stderr); } exec(命令,选项,之后);
  • 此选项可能会在没有命令行 mkdir 实例(即非 Linux-y 主机)的 node.js 平台上中断,因此它不可移植,如果这很重要的话。跨度>
  • @cshotton - 你指的是评论还是答案? shelljs 甚至可以在 Windows 上运行。 exec mkdir -p (评论)当然没有。
  • 这不是解决方案,这是解决方案的替代方案。上下文:pics.onsizzle.com/…
  • @NikaKasradze 这是一个可能的解决方案并且有效。所有解决方案都是替代方案。
【解决方案3】:

我以这种方式解决了这个问题 - 类似于其他递归答案,但对我来说这更容易理解和阅读。

const path = require('path');
const fs = require('fs');

function mkdirRecurse(inputPath) {
  if (fs.existsSync(inputPath)) {
    return;
  }
  const basePath = path.dirname(inputPath);
  if (fs.existsSync(basePath)) {
    fs.mkdirSync(inputPath);
  }
  mkdirRecurse(basePath);
}

【讨论】:

  • 如果我传入路径/does/not/exist,它只会创建第一级does文件夹;-(
  • 好的,我不知道为什么,但我认为在你的调试中很容易理解为什么。
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    我知道这是一个老问题,但 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

      【讨论】:

        【解决方案7】:

        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

        【讨论】:

        • 这是最好的答案!无论如何,我们大多数人已经在应用程序中安装了 fs-extra。
        • 如果它提供了使用memfs 进行单元测试的可能性,那就太好了。它没有:-( github.com/jprichardson/node-fs-extra/issues/274
        【解决方案8】:

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

        【讨论】:

        • 在给出答案时,最好给出some explanation as to WHY your answer
        • 对不起,你是对的,我认为这样更干净,更容易理解
        • @josebui 我认为最好使用“path.sep”而不是正斜杠 (/) 以避免环境特定问题。
        • 很好的解决方案,因为不像其他答案那样需要 node >=10
        【解决方案9】:

        你可以使用下一个功能

        const recursiveUpload =(路径:字符串)=> { 常量路径 = 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

        【讨论】:

          【解决方案10】:

          像这样干净:)

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

          【讨论】:

            【解决方案11】:

            您可以简单地递归检查文件夹是否存在于路径中,并在检查文件夹是否不存在时创建文件夹。 (没有外部库

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

            【讨论】:

              【解决方案12】:

              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
              

              【讨论】:

                【解决方案13】:
                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);
                }
                

                【讨论】:

                  【解决方案14】:

                  此功能已在 10.12.0 版本中添加到 node.js,因此只需将选项 {recursive: true} 作为第二个参数传递给 fs.mkdir() 调用即可。 请参阅example in the official docs

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

                  【讨论】:

                  • 我找到了相关的拉取请求github.com/nodejs/node/pull/23313
                  • 目录存在并停止时会报错。使用 try catch 块可以使其不断创建其他不存在的文件夹。
                  • 这应该是公认的答案。如果目录已经存在,它不会抛出,并且可以通过 fs.promises.mkdir 与 async/await 一起使用。
                  【解决方案15】:

                  此版本在 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?我有没有提到它也适用于其他操作系统?
                  【解决方案16】:

                  答案太多,但这里有一个没有递归的解决方案,它通过拆分路径然后从左到右重新构建它来工作

                  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 我们谈论的是 node fs 而不是 windows 命令行,前者非常宽容,上面的代码将只需在 Windows 上工作,是一个更完整的跨平台解决方案。

                  【讨论】:

                  • windows 上的文件使用反斜杠而不是正斜杠处理。您的代码根本无法在那里工作。 C:\data\test ...
                  • 已编辑,但建议您验证您的评论。在节点上尝试以下操作,看看会发生什么 var fs = require('fs') fs.mkdirSync('test') fs.mkdirSync('test\\test1') fs.mkdirSync('test/test2')
                  • 无论你在说什么......,在你学会编写更好的代码之前,我的反对票仍然存在。
                  • 哈哈。好的,我会努力学习如何编写更好的代码。顺便说一句,上面的大多数答案,包括 OP,都使用正斜杠。建议你停止拖钓。
                  • path.sep 对我来说是 / 或 \\ 。 path.delimiter 是 : 或 ;.
                  【解决方案17】:

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

                  }

                  【讨论】:

                    【解决方案18】:

                    基于mouneer's 零依赖答案,这里有一个对初学者更友好的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);
                    }
                    

                    【讨论】:

                      【解决方案19】:

                      这种方法怎么样:

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

                      这适用于相对路径。

                      【讨论】:

                        【解决方案20】:

                        这是我为 nodejs 编写的 mkdirp 命令式版本。

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

                        【讨论】:

                          【解决方案21】:

                          递归创建目录的异步方式:

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

                          【讨论】:

                            【解决方案22】:

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

                            【讨论】:

                            猜你喜欢
                            • 2020-01-22
                            • 1970-01-01
                            • 2013-12-25
                            • 2018-05-13
                            • 1970-01-01
                            • 2011-10-29
                            • 1970-01-01
                            • 2010-12-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多