如何创建一个目录,如果它不存在使用Node.js

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

它应该具有脚本的完全权限并可供其他人阅读。

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

不,出于多种原因。

  1. #0模块没有exists/existsSync方法。它在#3模块中。(也许你只是在你的问题中打错了?)

  2. 留档明确反对您使用exists

    fs.exists()是一个时代错误,只存在于历史原因。几乎不应该有理由在你自己的代码中使用它。

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

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

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

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


    我唯一一次考虑在服务器应用程序中使用*Sync方法是在启动时发生一次(并且只有一次)的操作中。例如,require实际使用#2加载模块。

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


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

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

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

我们可以这样使用它:

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

当然,这还不包括边缘情况,比如

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

对于个人目录:

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

我找到了一个npm模块,这就像一个魅力。

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

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

用途:

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

我想添加josh3736的答案的TypeScript Promise重构。

它做同样的事情并具有相同的边缘情况。它只是碰巧使用Promises、TypeScript typedef,并与“use严格”一起工作。

// https://en.wikipedia.org/wiki/File_system_permissions#Numeric_notationconst 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}});});}

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

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}, '')}
var dir = 'path/to/dir';try {fs.mkdirSync(dir);} catch(e) {if (e.code != 'EEXIST') throw e;}

使用async/wait:

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

您将需要promisifyfs

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

单行版本:

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

如果文件夹存在,您可以使用mkdir并捕获错误。
这是异步(所以最好的做法)和安全。

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

(可以选择使用mode添加第二个参数。)


其他思考:

  1. 您可以通过使用原生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. You can make your own promise method, something like (untested):

    let mkdirAsync = (path, mode) => new Promise((resolve, reject) => mkdir (path, mode,err => (err && err.code !== 'EEXIST') ? reject(err) : resolve()))
  3. For synchronous checking, you can use:

    fs.existsSync(path) || fs.mkdirSync(path)
  4. Or you can use a library, the two most popular being

    • mkdirp (just does folders)
    • fsextra (supersets fs, adds lots of useful stuff)

使用fs-额外包,您可以使用一句话执行此操作:

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

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

Node.jsv10/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.extensionfs.mkdir(path.resolve(__dirname, pathname), { recursive: true }, e => {if (e) {console.error(e);} else {console.log('Success');}});}

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

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

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

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

您可以使用Node.js文件系统命令fs.stat来检查目录是否存在,fs.mkdir来创建带有回调的目录,或者fs.mkdir同步来创建不带回调的目录,如下例:

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

Node.js10+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;}}})();

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

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

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

留档开始,这就是你异步(和递归)执行的方式:

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

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

// ensure-directory.jsimport { 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 welllet 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 downconst 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)}))}}}

我的解决方案

  1. 通用文档
var fs = require("fs");
var dir = __dirname + '/upload';
// if (!fs.existsSync(dir)) {//   fs.mkdirSync(dir);// }
if (!fs.existsSync(dir)) {fs.mkdirSync(dir, {mode: 0o744,});// mode's default value is 0o744}
  1. ESM

更新package.json配置

{//..."type": "module",//...}
import fs from "fs";import path from "path";
// create one custom `__dirname`, because it not exist in es-module env ⚠️const __dirname = path.resolve();
const dir = __dirname + '/upload';
if (!fs.existsSync(dir)) {fs.mkdirSync(dir);}
// ORif (!fs.existsSync(dir)) {fs.mkdirSync(dir, {mode: 0o744,});// mode's default value is 0o744}

裁判

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

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

解决方案

  1. 通用文档
const fs = require('fs');const path = require('path');
const dir = path.resolve(path.join(__dirname, 'upload');
if (!fs.existsSync(dir)) {fs.mkdirSync(dir);}
// ORif (!fs.existsSync(dir)) {fs.mkdirSync(dir, {mode: 0o744, // Not supported on Windows. Default: 0o777});}
  1. 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 does not exist in es-module env ⚠️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);}
// ORif (!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

对于节点v10及以上

正如一些答案指出的那样,由于节点10,您可以将recursive:true用于mkdir

还没有指出的是使用递归:true时,如果目录已经存在,mkdir不会返回错误

所以你可以这样做:

fsNative.mkdir(dirPath,{recursive:true},(err) => {if(err) {//note: this does NOT get triggered if the directory already existedconsole.warn(err)}else{//directory now exists}})

使用承诺

同样从节点10开始,您可以通过要求fromfs/promises来获得所有fs函数中的Promise版本

所以把这两件事放在一起,你会得到这个简单的解决方案:

import * as fs from 'fs/promises';
await fs.mkdir(dirPath, {recursive:true}).catch((err) => {//decide what you want to do if this failedconsole.error(err);});
//directory now exists