如何使用Node.js创建一个目录(文件夹)如果它不存在

1145

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

脚本应该拥有完全权限,并可被其他人阅读。

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

4
在提问之前,你尝试运行你的脚本了吗?当我尝试运行时,我收到了 TypeError: path.existsSync is not a function 的错误提示(我正在使用 Node v8.10)。 - Jean Paul
3
根据官方API文档 https://nodejs.org/api/fs.html#fsexistssyncpath,应该使用`fs.existsSync(dir)`而不是`path.existsSync(dir)`。 - xgqfrms
24个回答

2125

对于单个文件夹:

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

44
如果你在应用程序启动或初始化时执行此操作,则阻塞执行是可以的,因为如果你异步执行该操作也会做同样的事情。如果你正在创建一个文件夹作为一个经常性的操作,那么这是不好的习惯,但可能不会导致任何性能问题。只在启动应用程序或其他一次性操作时使用。 - tsturzl
28
existsSync() 没有被弃用,但 exists() 已经被弃用 - https://nodejs.org/api/fs.html#fs_fs_existssync_path - Ian Chadwick
2
使用*Sync方法通常是不可取的:不想阻塞事件循环。 - Max Heiber
29
使用同步方法在本地脚本等方面可以,但对于服务器来说显然不是一个好主意。 - Pier
3
如果存在同名文件会怎样?代码会继续执行,就像它是一个目录一样,但当尝试向其中的文件写入时可能会在稍后抛出错误。@josh3736的答案更加完整,却被低估了。 - Benni
显示剩余5条评论

241

不,由于多种原因。

  1. path 模块没有 exists/existsSync 方法,这个方法在fs模块中。 (也许你在问题中打错了?)

  2. 文档明确禁止使用 exists

    fs.exists() 是一种过时的存在,仅出于历史原因而存在。在您自己的代码中几乎永远不应该使用它。

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

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

  3. 通常情况下,应避免使用 *Sync 方法。 它们是阻塞的,这意味着在访问磁盘期间您的程序中绝对不能发生任何其他操作。 这是一项非常昂贵的操作,而它所花费的时间打破了 node 的事件循环的核心假设。

    *Sync 方法在单一用途的快速脚本(只执行一项任务然后退出)中通常是可以接受的,但是在编写服务器时几乎不应使用:您的服务器将无法响应任何人的 I/O 请求。 如果多个客户端请求需要 I/O 操作,则您的服务器将非常快地停止响应。


    我唯一考虑在服务器应用程序中使用 *Sync 方法的时间是在启动时发生 一次(仅一次)的操作。例如,require 实际上使用了 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
});

当然,这并不考虑边缘情况,比如:
  • 如果文件夹在程序运行时被删除会发生什么?(假设你只在启动时检查它是否存在)
  • 如果文件夹已经存在,但权限不正确会发生什么?

1
有没有办法避免在严格模式下出现 SyntaxError: 八进制字面量不被允许的错误? - Whisher
10
0744 转换为小数形式,等于 484 - josh3736
3
另一种选择是使用扩展fs的模块来实现此功能,例如https://github.com/jprichardson/node-fs-extra。 - Bret
1
这个“掩码”标志是否在2019年仍然相关?它的目的是什么? - oldboy
1
这是Unix文件模式,即目录的读/写权限。 - josh3736
3
无需将掩码写成十进制数,而是将其前缀改为0o而不是仅仅的0。 请参考https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Deprecated_octal - andreyrd

104

mkdir方法可以递归创建路径中不存在的任何目录,并忽略已存在的目录。

根据 Node.js v10/11 文档

// 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来更方便地实现可读性更高的同步方法,但这超出了问题的范围。


4
为什么要使用 const __dirname = path.resolve(); 而不是使用内置的 __dirname? - TamusJRoyce
2
@TamusJRoyce __dirname 在 es modules 中不可用。而且,path.resolve() 是 process.cwd(),无论如何都不是 __dirname。要获取正确的 __dirname,请参考:https://dev59.com/GGoy5IYBdhLWcg3wCpoz#62892482 - Erik Campobadal
@ErikCampobadal 很详细,没错。我之所以问是因为它似乎不正确。当发表这条评论时,ES模块并没有被广泛使用。虽然这个问题是关于Node.js的,但你的答案也适用于Deno - https://dev59.com/2VIH5IYBdhLWcg3wAHYv#61829368 - TamusJRoyce
那是关于演示兼容性的有用信息。太棒了! - Erik Campobadal
如果我想创建/var/log/a/b/c/,该如何创建?目前它总是抛出错误:Error: EACCES:权限被拒绝,mkdir。 - Mystic

50

使用fs-extra软件包,您可以通过一行代码来完成这个操作:

const fs = require('fs-extra');

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

3
这个回答被低估了!fs-extra 已经成为我必不可少的工具。我认为只为了检查文件夹是否存在而写10多行代码是荒谬的... - TOPKAT
2
虽然我希望这可以直接内置在核心功能中,但在我看来,这是最好的答案。简单而干净。 - Hajji Daoud
我数了三行。 - chovy

45

我找到了一个 npm模块,它非常适合这个需求。

它会在需要时进行递归的 mkdir 操作,就像执行“mkdir -p”命令一样简单。


6
为什么使用带有 { recursive: true } 标志的内置 mkdir 不如这种方法更好/不同? - Daniele Testa
4
希望这不是讽刺。引入第三方库来做一些非常基本且已经由内置模块实现的事情?这正是我们看到 JavaScript 生态系统混乱的精确原因。 - Zhe

41

简洁版:

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

13
所谓的一行代码实际上并不只有一行。 - Hybrid web dev
3
@Hybridwebdev 现在怎么样?;) const fs = require('fs'); !fs.existsSync(dir) && fs.mkdirSync(dir); (翻译:以上代码是用来检测指定目录是否存在,如果不存在则创建该目录的。) - Jack
8
将大量代码压缩到一行并不意味着它就成为了一行代码。 - Hybrid web dev
11
(fs => !fs.existsSync(dir) && fs.mkdirSync(dir))(require('fs'));(fs => !fs.existsSync(dir) && fs.mkdirSync(dir))(require('fs')); - KernelDeimos

28

解决方案

  1. CommonJS
const fs = require('fs');
const path = require('path');

const dir = path.resolve(path.join(__dirname, 'upload');

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

// OR
if (!fs.existsSync(dir)) {
  fs.mkdirSync(dir, {
    mode: 0o744, // Not supported on Windows. Default: 0o777
  });
}


2. ESM
更新你的 package.json 文件配置
{
  // declare using ECMAScript modules(ESM)
  "type": "module",
  //...
}

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

// create one custom `__dirname`, because it doesn't exist in es-module env.
// use `import.meta.url` to get the current module's URL, 
// then get the module's folder absolute path 
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const dir = path.resolve(path.join(__dirname, 'upload');

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

// OR
if (!fs.existsSync(dir)) {
  fs.mkdirSync(dir, {
    mode: 0o744, // Not supported on Windows. Default: 0o777
  });
}


更新2022年
import { existsSync } from 'node:fs';

参考

NodeJS 版本:v18.2.0

https://nodejs.org/api/fs.html#fsexistssyncpath

https://nodejs.org/api/fs.html#fsmkdirsyncpath-options

https://nodejs.org/api/url.html#urlfileurltopathurl

https://github.com/nodejs/help/issues/2907#issuecomment-757446568

ESM: ECMAScript模块

https://nodejs.org/api/esm.html#introduction


如果这与 TypeScript 有关,更新不应该在 tsconfig 中进行吗? - sagat
@sagat 这与 TypeScript 无关。 - Micka
谢谢澄清。我把compileOptions和package.json中的模块属性混淆了。 - sagat

22

如果文件夹已经存在,你可以直接使用 mkdir 并捕获错误。
这是异步的(最佳实践)和安全的。

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

(可选)使用第二个参数添加模式。


其他想法:

  1. 你可以使用原生的promisify来使用 then 或 await。

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() })
  • 你可以创建自己的 Promise 方法,例如(未经测试):

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

  • fs.existsSync(path) || fs.mkdirSync(path)
    
  • 或者您可以使用一个库,最受欢迎的有两个:

    • mkdirp(只创建文件夹)
    • fsextra(扩展了fs,添加了许多有用的功能)

  • 1
    对于有前途的方法#1,您可以重新排列catch。mkdir('/path').catch(err => { if (err.code != 'EEXIST') throw err;}).then(myFunc); - What Would Be Cool
    请使用 !== 替代 != - Quentin Roy

    19

    一行代码解决方案:如果目录不存在,则创建该目录

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

    1
    这有意义。 - Rigin Oommen

    12

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


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接