【问题标题】:How to create a directory if it doesn't exist using Node.js如果目录不存在,如何使用 Node.js 创建目录
【发布时间】:2014-02-07 07:36:11
【问题描述】:

如果目录不存在,以下是创建目录的正确方法吗?

它应该对脚本有完全的权限并且可以被其他人阅读。

var dir = __dirname + '/upload';
if (!path.existsSync(dir)) {
    fs.mkdirSync(dir, 0744);
}

【问题讨论】:

  • 在询问之前您是否尝试过运行您的脚本?当我尝试它时,我得到TypeError: path.existsSync is not a function(我使用的是节点 v8.10)。

标签: node.js


【解决方案1】:

不,有多种原因。

  1. path 模块没有exists/existsSync 方法。它位于fs 模块中。 (也许您只是在问题中打错字了?)

  2. The documentation explicitly discourage你使用exists

    fs.exists() 是不合时宜的,仅出于历史原因而存在。几乎不应该有理由在您自己的代码中使用它。

    特别是,在打开文件之前检查文件是否存在是一种反模式,这会使您容易受到竞争条件的影响:另一个进程可能会在调用 fs.exists()fs.open() 之间删除文件。只需打开文件并在错误不存在时处理它。

    由于我们讨论的是目录而不是文件,因此该建议意味着您应该无条件地调用 mkdir 并忽略 EEXIST

  3. 一般来说,您应该避免使用 *Sync 方法。它们正在阻塞,这意味着当您访问磁盘时,您的程序中绝对不会发生任何其他事情。这是一个非常昂贵的操作,所花费的时间打破了节点事件循环的核心假设。

    *Sync 方法通常在单一用途的快速脚本(那些做一件事然后退出的脚本)中很好,但在编写服务器时几乎不应该使用:在整个 I/O 请求期间,您的服务器将无法响应任何人。如果多个客户端请求需要 I/O 操作,您的服务器将很快停止运行。


    我会考虑在服务器应用程序中使用 *Sync 方法的唯一一次是在启动时发生一次(并且仅一次)的操作。比如requireactually uses readFileSync加载模块。

    即便如此,您仍然必须小心,因为大量同步 I/O 会不必要地减慢服务器的启动时间。


    相反,您应该使用异步 I/O 方法。

所以如果我们把这些建议放在一起,我们会得到这样的结果:

function ensureExists(path, mask, cb) {
    if (typeof mask == 'function') { // Allow the `mask` parameter to be optional
        cb = mask;
        mask = 0o744;
    }
    fs.mkdir(path, mask, function(err) {
        if (err) {
            if (err.code == 'EEXIST') cb(null); // Ignore the error if the folder already exists
            else cb(err); // Something else went wrong
        } else cb(null); // Successfully created folder
    });
}

我们可以这样使用它:

ensureExists(__dirname + '/upload', 0o744, function(err) {
    if (err) // Handle folder creation error
    else // We're all good
});

当然,这不考虑像

这样的极端情况
  • 如果在程序运行时文件夹被删除会怎样? (假设您只在启动期间检查它是否存在一次)
  • 如果文件夹已存在但权限错误,会发生什么情况?

【讨论】:

【解决方案2】:

对于单个目录:

var fs = require('fs');
var dir = './tmp';

if (!fs.existsSync(dir)){
    fs.mkdirSync(dir);
}

或者,对于嵌套目录:

var fs = require('fs');
var dir = './tmp/but/then/nested';

if (!fs.existsSync(dir)){
    fs.mkdirSync(dir, { recursive: true });
}

【讨论】:

  • 如果您在应用程序启动或初始化时执行此操作,则可以阻止执行,因为如果您要异步执行相同的操作。如果您将目录作为重复操作,那么这是一种不好的做法,但可能不会导致任何性能问题,但它仍然是一个坏习惯。仅用于启动您的应用程序或其他一次性操作。
  • existsSync() 没有被弃用,但是 exists() 是 - nodejs.org/api/fs.html#fs_fs_existssync_path
  • 使用 *Sync 方法通常是禁忌:不想阻塞事件循环
  • 使用同步方法对于本地脚本等来说很好,对于服务器来说显然不是一个好主意。
  • 如果存在同名文件怎么办?代码将继续,就好像有一个目录一样,并且稍后当它尝试写入其中的文件时可能会抛出错误。 @josh3736 的回答要完整得多,而且完全被低估了。
【解决方案3】:

我发现 an npm module 对此很有魅力。

它只是在需要时执行递归mkdir,例如“mkdir -p”。

【讨论】:

  • 为什么这与使用带有 { recursive: true } 标志的内置 mkdir 更好/不同?
  • 我希望这不是讽刺。引入第三方库只是为了做一些如此基本且已由内置模块实现的事情?这就是我们看到 JS 生态系统混乱的确切原因。
【解决方案4】:

最好的解决方案是使用名为 node-fs-extra 的 npm 模块。它有一个名为mkdir 的方法,可以创建您提到的目录。如果您提供较长的目录路径,它将自动创建父文件夹。该模块是npm模块fs的超集,所以如果你添加这个模块,你也可以使用fs中的所有功能。

【讨论】:

    【解决方案5】:

    用途:

    var filessystem = require('fs');
    var dir = './path/subpath/';
    
    if (!filessystem.existsSync(dir))
    {
        filessystem.mkdirSync(dir);
    }
    else
    {
        console.log("Directory already exist");
    }
    

    【讨论】:

    • 一个解释应该是有序的(但是没有“Edit:”、“Update:”或类似的 - 答案应该看起来好像今天写的)。
    【解决方案6】:

    我想添加一个 TypeScript Promise 重构 josh3736's answer

    它做同样的事情并且有同样的边缘情况。它只是碰巧使用了 Promises、TypeScript typedef,并与“use strict”一起使用。

    // https://en.wikipedia.org/wiki/File_system_permissions#Numeric_notation
    const allRWEPermissions = parseInt("0777", 8);
    
    function ensureFilePathExists(path: string, mask: number = allRWEPermissions): Promise<void> {
        return new Promise<void>(
            function(resolve: (value?: void | PromiseLike<void>) => void,
                reject: (reason?: any) => void): void{
                mkdir(path, mask, function(err: NodeJS.ErrnoException): void {
                    if (err) {
                        if (err.code === "EEXIST") {
                            resolve(null); // Ignore the error if the folder already exists
                        } else {
                            reject(err); // Something else went wrong
                        }
                    } else {
                        resolve(null); // Successfully created folder
                    }
                });
        });
    }
    

    【讨论】:

      【解决方案7】:

      这是一个递归创建目录的小函数:

      const createDir = (dir) => {
        // This will create a dir given a path such as './folder/subfolder' 
        const splitPath = dir.split('/');
        splitPath.reduce((path, subPath) => {
          let currentPath;
          if(subPath != '.'){
            currentPath = path + '/' + subPath;
            if (!fs.existsSync(currentPath)){
              fs.mkdirSync(currentPath);
            }
          }
          else{
            currentPath = subPath;
          }
          return currentPath
        }, '')
      }
      

      【讨论】:

        【解决方案8】:
        var dir = 'path/to/dir';
        try {
          fs.mkdirSync(dir);
        } catch(e) {
          if (e.code != 'EEXIST') throw e;
        }
        

        【讨论】:

        • 对于 Node.js v7.4.0,documentation 声明 fs.exists() 已弃用,但 fs.existsSync() 未弃用。您能否添加指向资源的链接,说明 fs.existsSync() 已贬值?
        • 纯代码的答案对以后遇到这个问题的用户帮助不大。请编辑您的答案以解释为什么您的代码解决了原始问题
        • @francis,嗯,我在看 Node.js v5,nodejs.org/docs/latest-v5.x/api/fs.html#fs_fs_existssync_path
        • 谢谢!似乎该功能在 0.12 版本中存在,在版本 4 和 5 中已弃用,并在版本 6 和 7 中恢复...有点僵尸功能...
        • 是的,从Apr 2018开始,显然它已被弃用:nodejs.org/api/fs.html#fs_fs_existssync_path
        【解决方案9】:

        使用异步/等待:

        const mkdirP = async (directory) => {
          try {
            return await fs.mkdirAsync(directory);
          } catch (error) {
            if (error.code != 'EEXIST') {
              throw e;
            }
          }
        };
        

        你需要承诺fs

        import nodeFs from 'fs';
        import bluebird from 'bluebird';
        
        const fs = bluebird.promisifyAll(nodeFs);
        

        【讨论】:

        • promisifyAll() 从何而来?节点.js?一些 Node.js 模块?还有什么?
        • 来自bluebird
        【解决方案10】:

        单行版本:

        // Or in TypeScript: import * as fs from 'fs';
        const fs = require('fs');
        !fs.existsSync(dir) && fs.mkdirSync(dir);
        

        【讨论】:

        • 所谓的 1-liner 实际上不是 1 行。
        • @Hybridwebdev 现在怎么样 ;) const fs = require('fs'); !fs.existsSync(dir) &amp;&amp; fs.mkdirSync(dir);
        • 将一堆代码混入 1 行并不能使其成为 1-liner。
        • (fs => !fs.existsSync(dir) && fs.mkdirSync(dir))(require('fs'));
        【解决方案11】:

        如果文件夹存在,您可以只使用mkdir 并捕获错误。
        这是异步的(因此是最佳实践)且安全。

        fs.mkdir('/path', err => { 
            if (err && err.code != 'EEXIST') throw 'up'
            .. safely do your stuff here  
            })
        

        (可以选择在模式中添加第二个参数。)


        其他想法:

        1. 您可以使用 then 或 await 使用原生 promisify

          const util = require('util'), fs = require('fs');
          const mkdir = util.promisify(fs.mkdir);
          var myFunc = () => { ..do something.. } 
          
          mkdir('/path')
              .then(myFunc)
              .catch(err => { if (err.code != 'EEXIST') throw err; myFunc() })
          
        2. 您可以制作自己的 promise 方法,例如(未经测试):

          let mkdirAsync = (path, mode) => new Promise(
             (resolve, reject) => mkdir (path, mode, 
                err => (err && err.code !== 'EEXIST') ? reject(err) : resolve()
                )
             )
          
        3. 对于同步检查,可以使用:

          fs.existsSync(path) || fs.mkdirSync(path)
          
        4. 或者你可以使用一个库,两个最流行的是

          • mkdirp(只做文件夹)
          • fsextra(超集 fs,添加了很多有用的东西)

        【讨论】:

        • 对于有希望的方法#1,您可以重新安排捕获。 mkdir('/path').catch(err =&gt; { if (err.code != 'EEXIST') throw err;}).then(myFunc);
        • 并使用!== 而不是!=
        【解决方案12】:

        使用fs-extra 包,您可以使用a one-liner 执行此操作:

        const fs = require('fs-extra');
        
        const dir = '/tmp/this/path/does/not/exist';
        fs.ensureDirSync(dir);
        

        【讨论】:

        • 这样一个被低估的答案! fs-extra 已经成为我的必备品。我认为写 10 多行来检查文件夹是否存在是一种异常......
        • 虽然我很乐意将其简单地融入核心功能,但这是 imo 的最佳答案。简单干净
        【解决方案13】:

        mkdir 方法能够递归地创建路径中不存在的任何目录,并忽略那些存在的目录。

        来自Node.js v10/11 documentation

        // Creates /tmp/a/apple, regardless of whether `/tmp` and /tmp/a exist.
        fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => {
            if (err) throw err;
        });
        

        注意:您需要先导入内置的fs 模块。

        现在这里有一个更强大的示例,它利用原生 ECMAScript 模块(启用标志和 .mjs 扩展名)、处理非根路径并考虑完整路径名:

        import fs from 'fs';
        import path from 'path';
        
        function createDirectories(pathname) {
           const __dirname = path.resolve();
           pathname = pathname.replace(/^\.*\/|\/?[^\/]+\.[a-z]+|\/$/g, ''); // Remove leading directory markers, and remove ending /file-name.extension
           fs.mkdir(path.resolve(__dirname, pathname), { recursive: true }, e => {
               if (e) {
                   console.error(e);
               } else {
                   console.log('Success');
               }
            });
        }
        

        你可以像createDirectories('/components/widget/widget.js');一样使用它。

        当然,您可能希望通过使用带有 async/await 的 Promise 在创建目录时以更具可读性的同步方式利用文件创建来获得更多花哨的功能;但是,这超出了问题的范围。

        【讨论】:

        • 为什么 const __dirname = path.resolve();而不是使用内置的 __dirname?
        • @TamusJRoyce __dirname 在 es 模块中不可用。此外,path.resolve() 是 process.cwd(),而不是 __dirname。要获得正确的 __dirname:stackoverflow.com/a/62892482/8280247
        • @ErikCampobadal 很好的细节。是的。我问是因为它似乎不正确。发布此评论时,es 模块并未被广泛使用。虽然这个问题是关于 node.js 的。您的答案也与 deno 兼容 - stackoverflow.com/a/61829368/458321
        • 那是关于演示兼容性的好消息。太棒了!
        • 如果我想创建/var/log/a/b/c/,如何创建?现在,它总是抛出错误:错误:EACCES:权限被拒绝,mkdir
        【解决方案14】:

        单行解决方案:如果目录存在则创建目录

        // import
        const fs = require('fs')  // In JavaScript
        import * as fs from "fs"  // in TypeScript
        import fs from "fs"       // in Typescript
        
        // Use
        !fs.existsSync(`./assets/`) && fs.mkdirSync(`./assets/`, { recursive: true })
        

        【讨论】:

        • 这是有道理的
        【解决方案15】:

        你可以使用Node.js File System命令fs.stat检查目录是否存在,fs.mkdir创建一个有回调的目录,或者 fs.mkdirSync 来创建一个没有回调的目录,比如这个例子:

        // First require fs
        const fs = require('fs');
        
        // Create directory if not exist (function)
        const createDir = (path) => {
            // Check if dir exist
            fs.stat(path, (err, stats) => {
                if (stats.isDirectory()) {
                    // Do nothing
                } else {
                    // If the given path is not a directory, create a directory
                    fs.mkdirSync(path);
                }
            });
        };
        

        【讨论】:

          【解决方案16】:

          使用 Node.js 10 + ES6:

          import path from 'path';
          import fs from 'fs';
          
          (async () => {
            const dir = path.join(__dirname, 'upload');
          
            try {
              await fs.promises.mkdir(dir);
            } catch (error) {
              if (error.code === 'EEXIST') {
                // Something already exists, but is it a file or directory?
                const lstat = await fs.promises.lstat(dir);
          
                if (!lstat.isDirectory()) {
                  throw error;
                }
              } else {
                throw error;
              }
            }
          })();
          

          【讨论】:

            【解决方案17】:

            如果子目录不存在,我必须创建它们。我用这个:

            const path = require('path');
            const fs = require('fs');
            
            function ensureDirectoryExists(p) {
                //console.log(ensureDirectoryExists.name, {p});
                const d = path.dirname(p);
                if (d && d !== p) {
                    ensureDirectoryExists(d);
                }
                if (!fs.existsSync(d)) {
                    fs.mkdirSync(d);
                }
            }
            

            【讨论】:

              【解决方案18】:

              fs.exist() 已弃用。所以我使用 fs.stat() 来检查目录状态。如果目录不存在,fs.stat() 会抛出错误,并显示类似“没有这样的文件或目录”的消息。然后我创建了一个目录。

              const fs = require('fs').promises;
              
              const dir = './dir';
              fs.stat(dir).catch(async (err) => {
                if (err.message.includes('no such file or directory')) {
                  await fs.mkdir(dir);
                }
              });
              

              【讨论】:

                【解决方案19】:

                来自the documentation,这是您异步(和递归)执行此操作的方式:

                const fs = require('fs');
                const fsPromises = fs.promises;
                
                fsPromises.access(dir, fs.constants.F_OK)
                   .catch(async() => {
                                await fs.mkdir(dir, { recursive: true }, function(err) {
                                    if (err) {
                                      console.log(err)
                                    }
                                  })
                    });
                

                【讨论】:

                  【解决方案20】:

                  异步执行此操作的函数(根据使用同步函数的 SO 上的类似答案调整,我现在找不到)

                  // ensure-directory.js
                  import { mkdir, access } from 'fs'
                  
                  /**
                   * directoryPath is a path to a directory (no trailing file!)
                   */
                  export default async directoryPath => {
                    directoryPath = directoryPath.replace(/\\/g, '/')
                  
                    // -- preparation to allow absolute paths as well
                    let root = ''
                    if (directoryPath[0] === '/') {
                      root = '/'
                      directoryPath = directoryPath.slice(1)
                    } else if (directoryPath[1] === ':') {
                      root = directoryPath.slice(0, 3) // c:\
                      directoryPath = directoryPath.slice(3)
                    }
                  
                    // -- create folders all the way down
                    const folders = directoryPath.split('/')
                    let folderPath = `${root}`
                    for (const folder of folders) {
                      folderPath = `${folderPath}${folder}/`
                  
                      const folderExists = await new Promise(resolve =>
                        access(folderPath, error => {
                          if (error) {
                            resolve(false)
                          }
                          resolve(true)
                        })
                      )
                  
                      if (!folderExists) {
                        await new Promise((resolve, reject) =>
                          mkdir(folderPath, error => {
                            if (error) {
                              reject('Error creating folderPath')
                            }
                            resolve(folderPath)
                          })
                        )
                      }
                    }
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2017-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2018-08-01
                    • 1970-01-01
                    • 2021-09-30
                    • 1970-01-01
                    相关资源
                    最近更新 更多