Làm cách nào để tạo đường dẫn đầy đủ với fs.mkdirSync của nút?


159

Tôi đang cố gắng tạo một đường dẫn đầy đủ nếu nó không tồn tại.

Mã trông như thế này:

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

Mã này hoạt động rất tốt miễn là chỉ có một thư mục con (một newDest như 'dir1') tuy nhiên khi có một đường dẫn thư mục như ('dir1 / dir2') thì nó không thành công với Error: ENOENT, không có tệp hoặc thư mục như vậy

Tôi muốn có thể tạo đường dẫn đầy đủ với ít dòng mã cần thiết.

Tôi đọc có một tùy chọn đệ quy trên fs và đã thử nó như thế này

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

Tôi cảm thấy thật đơn giản để tạo đệ quy một thư mục không tồn tại. Tôi có thiếu thứ gì không hay tôi cần phân tích đường dẫn và kiểm tra từng thư mục và tạo nó nếu nó không tồn tại?

Tôi khá mới với Node. Có lẽ tôi đang sử dụng một phiên bản cũ của FS?


1
github.com/substack/node-mkdirp và tất cả các loại giải pháp khác trên tìm kiếm Google này .
jfriend00

4
@AndyRay Câu hỏi StackOverflow này hiện là kết quả hàng đầu trong google cho câu hỏi này, điều này thật buồn cười bởi vì điều đó có nghĩa là nó tái diễn ....
Matt Parkins

1
Đó là một vấn đề trên các phiên bản cũ hơn của Node, cập nhật lên Node 12+ để giải quyết vấn đề
MrJomp

Câu trả lời:


48

Một lựa chọn là sử dụng mô-đun shelljs

npm cài đặt shelljs

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

Từ trang đó:

Tùy chọn có sẵn:

p: đường dẫn đầy đủ (sẽ tạo các thư mục trung gian nếu cần thiết)

Như những người khác đã lưu ý, có các mô-đun tập trung khác. Nhưng, bên ngoài mkdirp, nó có hàng tấn các hoạt động shell hữu ích khác (như, grep, v.v.) và nó hoạt động trên windows và * nix


2
Cảm ơn! Tôi đã kết thúc bằng cách sử dụng exec (tôi đã sử dụng cái này) và nó hoạt động như một bùa mê. var exec = Yêu cầu ('child_ process'). exec; lệnh var = "mkdir -p '" + newDest + "'"; tùy chọn var = {}; var after = function (error, stdout, stderr) {console.log ('error', error); console.log ('stdout', stdout); console.log ('stderr', stderr); } exec (lệnh, tùy chọn, sau);
David Silva Smith

24
Tùy chọn này có thể phá vỡ trên các nền tảng node.js không có phiên bản mkdir dòng lệnh (ví dụ: máy chủ không phải Linux-y) vì vậy nó không khả dụng, nếu điều đó quan trọng.
csh Bông

1
@csh Bông - bạn đang tham khảo bình luận hay câu trả lời? shelljs hoạt động ngay cả trên các cửa sổ. exec mkdir -p (bình luận) tất nhiên là không.
bryanmac

Bạn có thể sử dụng chức năng tuyệt vời này với Promise hoặc gọi lại theo lựa chọn của bạn.
Илья Зеленько

1
Đây không phải là một giải pháp, đây là một giải pháp thay thế. bối cảnh: pics.onsizzle.com/ từ
Nika Kasradze

412

Biên tập

Phiên bản NodeJS 10.12.0đã thêm một hỗ trợ riêng cho cả hai mkdirmkdirSyncđể tạo một thư mục đệ quy với recursive: truetùy chọn như sau:

fs.mkdirSync(targetDir, { recursive: true });

Và nếu bạn thích fs Promises API, bạn có thể viết

fs.promises.mkdir(targetDir, { recursive: true });

Câu trả lời gốc

Tạo thư mục đệ quy nếu chúng không tồn tại! ( Không phụ thuộc )

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

Sử dụng

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

Bản giới thiệu

Thử nó!

Giải thích

  • [CẬP NHẬT] Giải pháp này xử lý các lỗi EISDIRdành riêng cho nền tảng như cho Mac EPERMEACCEScho Windows. Cảm ơn tất cả các ý kiến ​​báo cáo của @PediT., @JohnQ, @ deed02392, @robyoder và @Almenon.
  • Giải pháp này xử lý cả đường dẫn tương đốituyệt đối . Cảm ơn bình luận của @john.
  • Trong trường hợp đường dẫn tương đối, thư mục đích sẽ được tạo (giải quyết) trong thư mục làm việc hiện tại. Để giải quyết chúng liên quan đến thư mục script hiện tại, hãy vượt qua {isRelativeToScript: true}.
  • Sử dụng path.seppath.resolve(), không chỉ /ghép nối, để tránh các vấn đề đa nền tảng.
  • Sử dụng fs.mkdirSyncvà xử lý các lỗi với try/catchnếu ném để xử lý điều kiện chủng tộc: quá trình khác có thể thêm các tập tin giữa các cuộc gọi đến fs.existsSync()fs.mkdirSync()và gây ra một ngoại lệ.
    • Một cách khác để đạt được điều đó có thể là kiểm tra nếu một tệp tồn tại rồi tạo nó, tức là if (!fs.existsSync(curDir) fs.mkdirSync(curDir);. Nhưng đây là một mô hình chống khiến mã dễ bị tổn thương trong điều kiện chủng tộc. Cảm ơn @GershomMaes bình luận về kiểm tra sự tồn tại của thư mục.
  • Yêu cầu Node v6 và mới hơn để hỗ trợ phá hủy. (Nếu bạn gặp sự cố khi triển khai giải pháp này với các phiên bản Node cũ, hãy để lại nhận xét cho tôi)

7
Upvote cho phản ứng dễ dàng, đệ quy không yêu cầu thêm thư viện hoặc phương pháp tiếp cận!
MikingTheViking

1
Thiếu câu lệnh yêu cầu: const fs = quiries ('fs'); const path = Yêu cầu ('đường dẫn');
Christopher Bull

1
@ChristopherBull, cố ý không thêm chỉ để tập trung vào logic, nhưng dù sao, tôi đã thêm chúng. Cảm ơn;)
Mouneer

1
12 dòng mã vững chắc, không phụ thuộc, tôi sẽ lấy nó mỗi lần.
psychboom

1
@Mouneer trên Mac OS X 10.12.6, lỗi được đưa ra khi cố gắng tạo "/" sau khi đi qua một đường dẫn tuyệt đối là "EISDIR" (Lỗi: EISDIR: hoạt động bất hợp pháp trên một thư mục, mkdir '/'). Tôi nghĩ có lẽ kiểm tra sự tồn tại của dir vẫn là cách tốt nhất để đi qua nền tảng (thừa nhận nó sẽ chậm hơn).
John Q

78

Một câu trả lời mạnh mẽ hơn là sử dụng sử dụng mkdirp .

var mkdirp = require('mkdirp');

mkdirp('/path/to/dir', function (err) {
    if (err) console.error(err)
    else console.log('dir created')
});

Sau đó tiến hành ghi tệp vào đường dẫn đầy đủ với:

fs.writeFile ('/path/to/dir/file.dat'....

Thích câu trả lời này vì bạn chỉ nhập những gì bạn cần chứ không phải toàn bộ thư viện
Juan Mendes

1
Chúc mừng cho huy hiệu Dân túy ;-)
janos

1
Cảm ơn. Đây là phương pháp tốt nhất.
Stepan Rafael


48

fs-Extra thêm các phương thức hệ thống tệp không có trong mô đun fs gốc. Nó là một sự thay thế cho fs.

Tải về fs-extra

$ npm install --save fs-extra

const fs = require("fs-extra");
// Make sure the output directory is there.
fs.ensureDirSync(newDest);

Có các tùy chọn đồng bộ và không đồng bộ.

https://github.com/jprichardson/node-fs-extra/blob/master/docs/ensureDir.md


5
Đây là câu trả lời tốt nhất! Hầu hết chúng ta đều đã có fs-Extra trong ứng dụng.
trang

Điều này sẽ là tuyệt vời nếu nó cung cấp một khả năng sử dụng memfsđể thử nghiệm đơn vị. Nó không :-( github.com/jprichardson/node-fs-extra/issues/274
schnatterer

31

Sử dụng giảm chúng ta có thể xác minh nếu mỗi đường dẫn tồn tại và tạo nó nếu cần thiết, cũng theo cách này tôi nghĩ nó dễ theo dõi hơn. Đã chỉnh sửa, cảm ơn @Arvin, chúng ta nên sử dụng path.sep để có được dấu phân cách đường dẫn dành riêng cho nền tảng.

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


Xin lỗi, bạn đã đúng, tôi nghĩ rằng cách này sạch sẽ và dễ theo dõi hơn
josebui

4
@josebui Tôi nghĩ rằng tốt hơn là sử dụng "path.sep" thay vì dấu gạch chéo (/) để tránh các vấn đề cụ thể về môi trường.
Arvin

giải pháp tốt vì không yêu cầu nút> = 10 như các câu trả lời khác
Karim

29

Tính năng này đã được thêm vào node.js trong phiên bản 10.12.0, do đó, dễ dàng chuyển một tùy chọn {recursive: true}làm đối số thứ hai cho fs.mkdir()cuộc gọi. Xem ví dụ trong các tài liệu chính thức .

Không cần các mô-đun bên ngoài hoặc thực hiện của riêng bạn.


1
Tôi tìm thấy yêu cầu kéo liên quan github.com/nodejs/node/pull/23313
Nurettin

1
Nó sẽ ném lỗi khi thư mục tồn tại và dừng lại. Sử dụng một khối thử bắt có thể làm cho nó tiếp tục tạo thư mục không tồn tại khác.
Choco Li

1
Đây phải là câu trả lời được chấp nhận. Nó không ném nếu thư mục đã tồn tại và có thể được sử dụng với async / await thông qua fs.promises.mkdir.
Giàu Apodaca

7

Tôi biết đây là một câu hỏi cũ, nhưng nodejs v10.12.0 hiện hỗ trợ điều này tự nhiên với recursivetùy chọn được đặt thành đúng. 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

Ví dụ cho Windows (không phụ thuộc thêm và xử lý lỗi)

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

Bạn có thể chỉ cần kiểm tra thư mục tồn tại hay không trong đường dẫn đệ quy và tạo thư mục khi bạn kiểm tra nếu chúng không có mặt. ( KHÔNG CÓ THƯ VIỆN NGOẠI THẤT )

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

Bạn có thể sử dụng chức năng tiếp theo

const recursiveUpload = (path: string) => {const path = path.split ("/")

const fullPath = paths.reduce((accumulator, current) => {
  fs.mkdirSync(accumulator)
  return `${accumulator}/${current}`
  })

  fs.mkdirSync(fullPath)

  return fullPath
}

Vì vậy, những gì nó làm:

  1. Tạo pathsbiến, trong đó nó lưu trữ mọi đường dẫn của chính nó như là một phần tử của mảng.
  2. Thêm "/" vào cuối mỗi phần tử trong mảng.
  3. Làm cho chu kỳ:
    1. Tạo một thư mục từ ghép các phần tử mảng có chỉ mục từ 0 đến lần lặp hiện tại. Về cơ bản, nó là đệ quy.

Mong rằng sẽ giúp!

Nhân tiện, trong Node v10.12.0, bạn có thể sử dụng tạo đường dẫn đệ quy bằng cách đưa nó làm đối số bổ sung.

fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => { if (err) throw err; });

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


1

Quá nhiều câu trả lời, nhưng đây là một giải pháp không có đệ quy hoạt động bằng cách tách đường dẫn và sau đó xây dựng lại từ trái sang phải để sao lưu lại

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

Đối với những người quan tâm về khả năng tương thích giữa Windows và Linux, chỉ cần thay thế dấu gạch chéo ngược bằng dấu gạch chéo kép '\' trong cả hai lần xuất hiện ở trên nhưng TBH chúng ta đang nói về nút fs không phải dòng lệnh windows và mã trước đây khá dễ tha thứ và đoạn mã trên sẽ đơn giản hoạt động Windows và là một giải pháp đa nền tảng hoàn chỉnh.


các tập tin trên windows được xử lý với dấu gạch chéo ngược không chuyển tiếp dấu gạch chéo. Mã của bạn chỉ đơn giản là không hoạt động ở đó. C: \ data \ test ...
DDD

Đã chỉnh sửa nhưng đề nghị bạn xác nhận nhận xét của bạn. Mở nút thử như sau và xem những gì sẽ xảy ra var fs = require ( 'fs') fs.mkdirSync ( 'test') fs.mkdirSync ( 'test \\ test1') fs.mkdirSync ( 'test / test2')
Hamiora

Dù bạn đang nói gì, phiếu bầu của tôi vẫn ở lại cho đến khi bạn học cách viết mã tốt hơn.
DDD

Haha. Ok, tôi sẽ làm việc rất chăm chỉ để học cách viết mã tốt hơn. BTW hầu hết các câu trả lời ở trên, bao gồm cả OP, sử dụng dấu gạch chéo về phía trước. Đề nghị bạn ngừng trolling.
Hamiora

1
path.sep đang đến như là / hoặc \\ cho tôi. path.delimiterlà: hoặc;
Josh Anderson Slate

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

Một cách không đồng bộ để tạo thư mục đệ quy:

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

Đây là phiên bản bắt buộc của tôi mkdirpcho 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

Cách tiếp cận này:

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

Điều này làm việc cho đường dẫn tương đối.


0

Dựa trên câu trả lời không phụ thuộc của mouneer , đây là một Typescriptbiến thể thân thiện với người mới bắt đầu hơn một chút , dưới dạng một mô-đun:

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

Sạch sẽ như thế này :)

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

Tôi gặp vấn đề với tùy chọn đệ quy của fs.mkdir vì vậy tôi đã thực hiện một chức năng thực hiện như sau:

  1. Tạo một danh sách tất cả các thư mục, bắt đầu với thư mục đích cuối cùng và làm việc với cha mẹ gốc.
  2. Tạo một danh sách mới các thư mục cần thiết để hàm mkdir hoạt động
  3. Làm cho mỗi thư mục cần thiết, bao gồm cả cuối cùng

    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 có thể lộn xộn trên windows. Có một giải pháp "gật đầu" hơn. Về cơ bản, bạn có một cuộc gọi đệ quy để xem liệu một thư mục có tồn tại và lao vào đứa trẻ (nếu nó tồn tại) hoặc tạo ra nó. Đây là một chức năng sẽ tạo ra các con và gọi một hàm khi kết thúc:

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

Phiên bản này hoạt động tốt hơn trên Windows so với câu trả lời hàng đầu vì nó hiểu cả hai /path.sepdo đó, dấu gạch chéo phía trước hoạt động trên Windows như bình thường. Hỗ trợ các đường dẫn tuyệt đối và tương đối (liên quan đến 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;
}

Là downvote để hỗ trợ Windows chính xác? Tôi đã đề cập đến nó hoạt động trên các hệ điều hành khác?
Qwertie
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.