Cách nhanh nhất để sao chép tệp trong tệp node.js


488

Dự án mà tôi đang làm việc (node.js) ngụ ý rất nhiều hoạt động với hệ thống tệp (sao chép / đọc / ghi, v.v.). Tôi muốn biết phương pháp nào là nhanh nhất và tôi rất vui khi nhận được lời khuyên. Cảm ơn.


42
Đó là một câu hỏi hay, mặc dù thật thú vị khi nó nhận được 25 lượt upvote khi các câu hỏi định dạng tương tự khác sẽ nhận được 3 hoặc 4 lượt tải xuống ngay lập tức vì không đáp ứng "tiêu chuẩn" SO (có thể thẻ javascript được thu thập bởi những người tử tế hơn :)
Ben

22
Hầu hết chúng ta chỉ mới mẻ và hào hứng với toàn bộ "tập tin" kinh doanh này sau nhiều năm bình thường hóa trình duyệt.
Erik Reppen

3
Câu trả lời đúng duy nhất trên trang này là câu này . Không có câu trả lời nào khác thực sự sao chép các tập tin. Các tệp trên MacOS và Windows có siêu dữ liệu khác bị mất chỉ bằng cách sao chép byte. Ví dụ về dữ liệu không được sao chép bởi bất kỳ câu trả lời nào khác trên trang này, windowsmacos . Ngay cả trên Unix, các câu trả lời khác không sao chép ngày tạo, một điều thường quan trọng khi sao chép tệp.
gman

Câu trả lời:


717

Đây là một cách tốt để sao chép một tệp trong một dòng mã bằng các luồng:

var fs = require('fs');

fs.createReadStream('test.log').pipe(fs.createWriteStream('newLog.log'));

Trong nút v8.5.0, copyFile đã được thêm vào

const fs = require('fs');

// destination.txt will be created or overwritten by default.
fs.copyFile('source.txt', 'destination.txt', (err) => {
  if (err) throw err;
  console.log('source.txt was copied to destination.txt');
});

64
Chỉ cần nhớ rằng trong cuộc sống thực, bạn muốn kiểm tra cả lỗi createReadStreamcreateWriteStreamlỗi, vì vậy bạn sẽ không nhận được một lớp lót (mặc dù nó sẽ vẫn nhanh như vậy).
ebohlman

18
Làm thế nào nhanh hơn / chậm hơn nhiều so với thực hiện thô cp test.log newLog.logthông qua require('child_process').exec?
Lance Pollard

41
Vâng copykhông khả dụng trên Window, trái với giải pháp Node.js đầy đủ.
Jean

12
Thật không may trên hệ thống của tôi sử dụng luồng là rất chậm so với child_process.execFile('/bin/cp', ['--no-target-directory', source, target]).
Robert

12
Tôi đã sử dụng phương pháp này và tất cả những gì tôi nhận được là một tập tin trống khi viết. bất cứ ý tưởng tại sao? fs.createReadStream('./init/xxx.json').pipe(fs.createWriteStream('xxx.json'));
Timmerz

293

Cùng một cơ chế, nhưng điều này thêm xử lý lỗi:

function copyFile(source, target, cb) {
  var cbCalled = false;

  var rd = fs.createReadStream(source);
  rd.on("error", function(err) {
    done(err);
  });
  var wr = fs.createWriteStream(target);
  wr.on("error", function(err) {
    done(err);
  });
  wr.on("close", function(ex) {
    done();
  });
  rd.pipe(wr);

  function done(err) {
    if (!cbCalled) {
      cb(err);
      cbCalled = true;
    }
  }
}

5
Điều đáng chú ý là cờ cbCalled là cần thiết vì lỗi đường ống gây ra lỗi trên cả hai luồng. Nguồn và luồng đích.
Gaston Sanchez

4
Làm thế nào để bạn xử lý lỗi nếu tệp nguồn không tồn tại? Tệp đích vẫn được tạo trong trường hợp đó.
Michel Hua

1
Tôi nghĩ rằng một lỗi trong di WriteStreamchúc chỉ unpipe nó. Bạn sẽ phải gọi rd.destroy()cho mình. Ít nhất đó là những gì đã xảy ra với tôi. Đáng buồn là không có nhiều tài liệu ngoại trừ từ mã nguồn.
Robert

những gì hiện cbđứng cho? những gì chúng ta nên vượt qua như là đối số thứ ba?
SaiyanGirl

4
@SaiyanGirl 'cb' là viết tắt của "gọi lại". Bạn nên vượt qua trong một chức năng.
Brian J. Miller

143

Tôi không thể khiến createReadStream/createWriteStreamphương thức hoạt động vì một số lý do, nhưng sử dụng fs-extramô-đun npm thì nó hoạt động ngay lập tức. Tôi không chắc chắn về sự khác biệt hiệu suất mặc dù.

fs-thêm

npm install --save fs-extra

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

fs.copySync(path.resolve(__dirname,'./init/xxx.json'), 'xxx.json');

3
Đây là lựa chọn tốt nhất bây giờ
Zain Rizvi

11
Sử dụng mã đồng bộ trong nút sẽ giết chết hiệu suất ứng dụng của bạn.
mvillar

3
Oh làm ơn ... Câu hỏi là về phương pháp nhanh nhất để sao chép một tập tin. Mặc dù nhanh nhất luôn mang tính chủ quan, tôi không nghĩ rằng một đoạn mã đồng bộ có bất kỳ hoạt động kinh doanh nào ở đây.
sampathsris

24
Nhanh nhất để thực hiện hay nhanh nhất để thực hiện? Các ưu tiên khác nhau có nghĩa đây là một câu trả lời hợp lệ.
Patrick Gunderson

14
fs-Extra cũng có các phương thức không đồng bộ, nghĩa là fs.copy(src, dst, callback);, và chúng sẽ giải quyết mối quan tâm của @ mvillar.
Marc Durdin

134

Kể từ Node.js 8.5.0, chúng tôi có fs.copyFilefs.copyFileSync mới phương pháp.

Ví dụ sử dụng:

var fs = require('fs');

// destination.txt will be created or overwritten by default.
fs.copyFile('source.txt', 'destination.txt', (err) => {
    if (err) throw err;
    console.log('source.txt was copied to destination.txt');
});

2
Đây là câu trả lời đúng duy nhất trên trang. Không có câu trả lời nào khác thực sự sao chép các tập tin. Các tệp trên MacOS và Windows có siêu dữ liệu khác bị mất chỉ bằng cách sao chép byte. Ví dụ về dữ liệu không được sao chép bởi bất kỳ câu trả lời nào khác trên trang này, windowsmacos . Ngay cả trên Unix, câu trả lời khác không sao chép ngày tạo, một điều thường quan trọng khi sao chép tệp.
gman

thật đáng buồn khi điều này không thể sao chép mọi thứ trên mac. Hy vọng họ sẽ sửa nó: github.com/nodejs/node/issues/30575
gman

BTW hãy nhớ rằng đã copyFile()bị lỗi trong khi ghi đè các tệp dài hơn. Phép lịch sự uv_fs_copyfile()cho đến Node v8.7.0 (libuv 1.15.0). xem github.com/libuv/libuv/pull/1552
Anton Rudeshko ngày

74

Nhanh chóng để viết và thuận tiện để sử dụng, với lời hứa và quản lý lỗi.

function copyFile(source, target) {
  var rd = fs.createReadStream(source);
  var wr = fs.createWriteStream(target);
  return new Promise(function(resolve, reject) {
    rd.on('error', reject);
    wr.on('error', reject);
    wr.on('finish', resolve);
    rd.pipe(wr);
  }).catch(function(error) {
    rd.destroy();
    wr.end();
    throw error;
  });
}

Tương tự với cú pháp async / await:

async function copyFile(source, target) {
  var rd = fs.createReadStream(source);
  var wr = fs.createWriteStream(target);
  try {
    return await new Promise(function(resolve, reject) {
      rd.on('error', reject);
      wr.on('error', reject);
      wr.on('finish', resolve);
      rd.pipe(wr);
    });
  } catch (error) {
    rd.destroy();
    wr.end();
    throw error;
  }
}

1
Điều gì xảy ra khi không còn đầu vào tồn tại (chia sẻ mạng bị hỏng), nhưng ghi vẫn thành công? Cả hai từ chối (từ đọc) và giải quyết (từ viết) sẽ được gọi? Điều gì xảy ra nếu cả đọc / ghi không thành công (các thành phần đĩa xấu trong khi đọc, toàn bộ đĩa trong khi ghi)? Sau đó từ chối sẽ được gọi hai lần. Một giải pháp Promise dựa trên câu trả lời của Mike với một lá cờ (thật không may) dường như là giải pháp khả thi duy nhất xem xét đúng cách xử lý lỗi.
Lekensteyn

Lời hứa được giải quyết một khi bản sao thành công. Nếu nó bị từ chối, trạng thái của nó được giải quyết và gọi từ chối nhiều lần sẽ không có gì khác biệt.
benweet

2
Tôi chỉ thử nghiệm new Promise(function(resolve, reject) { resolve(1); resolve(2); reject(3); reject(4); console.log("DONE"); }).then(console.log.bind(console), function(e){console.log("E", e);});và tra cứu thông số kỹ thuật về điều này và bạn đã đúng: Cố gắng giải quyết hoặc từ chối lời hứa đã giải quyết không có kết quả. Có lẽ bạn có thể mở rộng câu trả lời của mình và giải thích lý do tại sao bạn đã viết hàm theo cách này? Cảm ơn :-)
Lekensteyn

2
Nhân tiện, closenên finishdành cho các luồng Writable.
Lekensteyn

Và nếu bạn tự hỏi tại sao ứng dụng của bạn không bao giờ đóng sau lỗi đường ống /dev/stdin, thì đó là lỗi github.com/joyent/node/issues/25375
Lekensteyn

43

Chà, thường thì tốt để tránh các hoạt động tập tin không đồng bộ. Dưới đây là ví dụ đồng bộ hóa ngắn (nghĩa là không xử lý lỗi):

var fs = require('fs');
fs.writeFileSync(targetFile, fs.readFileSync(sourceFile));

8
Nói rằng nói chung là vô cùng sai lầm, đặc biệt là vì nó dẫn đến việc mọi người lại bôi nhọ các tệp cho mọi yêu cầu được gửi đến máy chủ của họ. Điều này có thể nhận được đắt tiền.
Chất xúc tác

8
sử dụng các *Syncphương thức hoàn toàn chống lại triết lý của nodejs! Tôi cũng nghĩ rằng họ đang dần bị phản đối. Toàn bộ ý tưởng của nodejs là nó đơn luồng và theo sự kiện.
gillyb

11
@gillyb Lý do duy nhất tôi có thể nghĩ đến khi sử dụng chúng là vì đơn giản - nếu bạn đang viết một kịch bản nhanh mà bạn sẽ chỉ sử dụng một lần, có lẽ bạn sẽ không bận tâm về việc chặn quá trình.
starbeamrainbowlabs

13
Tôi không nhận thức được chúng bị phản đối. Các phương thức đồng bộ hóa hầu như luôn là một ý tưởng tồi tệ trên máy chủ web nhưng đôi khi lý tưởng trong một cái gì đó như nút-webkit nơi nó chỉ khóa hành động trong cửa sổ trong khi các tệp đang sao chép. Ném lên một gif đang tải và có thể là một thanh tải cập nhật tại một số điểm nhất định và để các phương thức đồng bộ hóa chặn tất cả hành động cho đến khi sao chép xong. Nó không thực sự là một điều thực hành tốt nhất nhiều như khi họ và nơi họ có thứ của họ.
Erik Reppen

6
Phương thức đồng bộ hóa tốt khi bạn tương tác với một hoạt động đồng bộ hóa khác hoặc điều bạn muốn là thực hiện thao tác tuần tự (nghĩa là dù sao bạn cũng sẽ mô phỏng đồng bộ hóa). Nếu các hoạt động là tuần tự, chỉ cần tránh địa ngục gọi lại (và / hoặc súp hứa hẹn) và sử dụng phương pháp đồng bộ hóa. Nói chung, chúng nên được sử dụng thận trọng trên các máy chủ nhưng tốt cho hầu hết các trường hợp liên quan đến tập lệnh CLI.
srcspider

18

Giải pháp của Mike Schilling với việc xử lý lỗi với một phím tắt cho trình xử lý sự kiện lỗi.

function copyFile(source, target, cb) {
  var cbCalled = false;

  var rd = fs.createReadStream(source);
  rd.on("error", done);

  var wr = fs.createWriteStream(target);
  wr.on("error", done);
  wr.on("close", function(ex) {
    done();
  });
  rd.pipe(wr);

  function done(err) {
    if (!cbCalled) {
      cb(err);
      cbCalled = true;
    }
  }
}

18

Nếu bạn không quan tâm đến việc nó không đồng bộ và không sao chép các tệp có kích thước gigabyte và không muốn thêm một phụ thuộc khác chỉ cho một chức năng duy nhất:

function copySync(src, dest) {
  var data = fs.readFileSync(src);
  fs.writeFileSync(dest, data);
}

4
Tôi thích câu trả lời này. Rõ ràng và đơn giản.
Rob Gleeson

7
@RobGleeson, và yêu cầu nhiều bộ nhớ như nội dung tệp ... Tôi ngạc nhiên bởi số lượng upvote ở đó.
Konstantin

Tôi đã thêm một cảnh báo "và không sao chép các tệp có kích thước gigabyte".
Andrew Childs

Cuộc fs.existsSyncgọi nên được bỏ qua. Tập tin có thể biến mất trong khoảng thời gian giữa fs.existsSynccuộc gọi và fs.readFileSynccuộc gọi, điều đó có nghĩa là fs.existsSynccuộc gọi không bảo vệ chúng tôi khỏi bất cứ điều gì.
qntm

Ngoài ra, việc trả lại falsenếu fs.existsSyncthất bại có khả năng là công thái học kém vì ít người tiêu dùng copySyncsẽ nghĩ sẽ tự kiểm tra giá trị trả lại mỗi khi nó được gọi, nhiều hơn chúng ta làm cho fs.writeFileSync et al. . Ném một ngoại lệ là thực sự thích hợp hơn.
qntm

2
   const fs = require("fs");
   fs.copyFileSync("filepath1", "filepath2"); //fs.copyFileSync("file1.txt", "file2.txt");

Đây là những gì cá nhân tôi sử dụng để sao chép một tệp và thay thế một tệp khác bằng node.js :)


1
Điều này không trả lời câu hỏi, đó là về cách sao chép hiệu quả các tệp trong ứng dụng nặng IO.
Jared Smith

@JaredSmith Đúng, nhưng tìm kiếm google của tôi dẫn tôi đến đây và đây là những gì tôi muốn.
codepleb

1

Đối với các bản sao nhanh, bạn nên sử dụng fs.constants.COPYFILE_FICLONEcờ. Nó cho phép (đối với các hệ thống tệp hỗ trợ này) không thực sự sao chép nội dung của tệp. Chỉ một mục nhập tệp mới được tạo, nhưng nó trỏ đến Sao chép trên ghi " của tệp nguồn.

Không làm gì / ít hơn là cách nhanh nhất để làm một cái gì đó;)

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

let fs = require("fs");

fs.copyFile(
  "source.txt",
  "destination.txt",
  fs.constants.COPYFILE_FICLONE,
  (err) => {
    if (err) {
      // TODO: handle error
      console.log("error");
    }
    console.log("success");
  }
);

Sử dụng lời hứa thay thế:

let fs = require("fs");
let util = require("util");
let copyFile = util.promisify(fs.copyFile);


copyFile(
  "source.txt",
  "destination.txt",
  fs.constants.COPYFILE_FICLONE
)
  .catch(() => console.log("error"))
  .then(() => console.log("success"));

fs.promises.copyFile
gman

0

Giải pháp của benweet kiểm tra mức độ hiển thị của tệp trước khi sao chép:

function copy(from, to) {
    return new Promise(function (resolve, reject) {
        fs.access(from, fs.F_OK, function (error) {
            if (error) {
                reject(error);
            } else {
                var inputStream = fs.createReadStream(from);
                var outputStream = fs.createWriteStream(to);

                function rejectCleanup(error) {
                    inputStream.destroy();
                    outputStream.end();
                    reject(error);
                }

                inputStream.on('error', rejectCleanup);
                outputStream.on('error', rejectCleanup);

                outputStream.on('finish', resolve);

                inputStream.pipe(outputStream);
            }
        });
    });
}

0

Tại sao không sử dụng nodejs được xây dựng trong chức năng sao chép?

Nó cung cấp cả phiên bản không đồng bộ và đồng bộ hóa:

const fs = require('fs');

// destination.txt will be created or overwritten by default.
fs.copyFile('source.txt', 'destination.txt', (err) => {
  if (err) throw err;
  console.log('source.txt was copied to destination.txt');
});

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


3
Không nâng cao vì câu trả lời này là một bản sao.
Qwertie

-1

Giải pháp của Mike , nhưng với những lời hứa:

const FileSystem = require('fs');

exports.copyFile = function copyFile(source, target) {
    return new Promise((resolve,reject) => {
        const rd = FileSystem.createReadStream(source);
        rd.on('error', err => reject(err));
        const wr = FileSystem.createWriteStream(target);
        wr.on('error', err => reject(err));
        wr.on('close', () => resolve());
        rd.pipe(wr);
    });
};

@Royi Vì tôi muốn một giải pháp không đồng bộ ...?
mở

-1

Cải thiện một câu trả lời khác.

Đặc trưng:

  • Nếu các thư mục dst không tồn tại, nó sẽ tự động tạo nó. Câu trả lời khác sẽ chỉ ném lỗi.
  • Nó trả về một promise, làm cho nó dễ sử dụng hơn trong một dự án lớn hơn.
  • Nó cho phép bạn sao chép nhiều tệp và lời hứa sẽ được thực hiện khi tất cả chúng được sao chép.

Sử dụng:

var onePromise = copyFilePromise("src.txt", "dst.txt");
var anotherPromise = copyMultiFilePromise(new Array(new Array("src1.txt", "dst1.txt"), new Array("src2.txt", "dst2.txt")));

Mã số:

function copyFile(source, target, cb) {
    console.log("CopyFile", source, target);

    var ensureDirectoryExistence = function (filePath) {
        var dirname = path.dirname(filePath);
        if (fs.existsSync(dirname)) {
            return true;
        }
        ensureDirectoryExistence(dirname);
        fs.mkdirSync(dirname);
    }
    ensureDirectoryExistence(target);

    var cbCalled = false;
    var rd = fs.createReadStream(source);
    rd.on("error", function (err) {
        done(err);
    });
    var wr = fs.createWriteStream(target);
    wr.on("error", function (err) {
        done(err);
    });
    wr.on("close", function (ex) {
        done();
    });
    rd.pipe(wr);
    function done(err) {
        if (!cbCalled) {
            cb(err);
            cbCalled = true;
        }
    }
}

function copyFilePromise(source, target) {
    return new Promise(function (accept, reject) {
        copyFile(source, target, function (data) {
            if (data === undefined) {
                accept();
            } else {
                reject(data);
            }
        });
    });
}

function copyMultiFilePromise(srcTgtPairArr) {
    var copyFilePromiseArr = new Array();
    srcTgtPairArr.forEach(function (srcTgtPair) {
        copyFilePromiseArr.push(copyFilePromise(srcTgtPair[0], srcTgtPair[1]));
    });
    return Promise.all(copyFilePromiseArr);
}

-2

tất cả các giải pháp trên không kiểm tra sự tồn tại của tệp nguồn đều nguy hiểm ... ví dụ:

fs.stat(source, function(err,stat) { if (err) { reject(err) }

nếu không, có một rủi ro trong một kịch bản trong trường hợp nguồn và đích bị lỗi thay thế, dữ liệu của bạn sẽ bị mất vĩnh viễn mà không nhận thấy bất kỳ lỗi nào.


Điều này cũng có một điều kiện chủng tộc: tập tin có thể bị hủy giữa stat-ing và đọc / ghi / sao chép. Luôn luôn tốt hơn khi chỉ thử hoạt động và xử lý bất kỳ lỗi nào.
Jared Smith

kiểm tra sự tồn tại của mục tiêu trước khi thao tác ghi đảm bảo bạn không ghi đè lên mục tiêu một cách tình cờ, ví dụ như bao gồm một kịch bản mà đích và nguồn được đặt bởi người dùng do nhầm lẫn giống nhau ... đã muộn khi chờ thao tác ghi không thành công ... whover đã cho tôi (-1) xin vui lòng xem lại thứ hạng của bạn một khi sự cố này xảy ra trong dự án của bạn :-) re. các cuộc đua - trên các trang web nặng nề luôn được khuyến nghị để có một hoạt động xử lý quy trình yêu cầu đảm bảo đồng bộ hóa - vâng, đó là nút cổ chai hiệu năng
stancikcom

Tôi đã không downvote vì bạn sai , tôi downvote vì đây không phải là một câu trả lời cho câu hỏi. Nó nên là một bình luận thận trọng về một câu trả lời hiện có.
Jared Smith

tốt - bạn là một giải pháp đúng, ví dụ như andrew childrens (với 18 upvote) sẽ hết tài nguyên trên một máy chủ / tệp lớn ... tôi sẽ viết bình luận cho anh ấy nhưng tôi không có tiếng để bình luận - vì vậy bạn đã thấy bài đăng của tôi độc lập. ... nhưng Jared hạ cấp của bạn có nghĩa là một cách đơn giản đối với tôi - giữ im lặng và để mọi người viết và chia sẻ mã nguy hiểm mà chủ yếu là "hoạt động" ...
stancikcom

Tôi nhận được nó, không ai thích phản hồi tiêu cực. Nhưng nó chỉ là một downvote. Tôi đứng trước lý do của mình khi đưa ra, vì điều này không trả lời câu hỏi mà OP đã hỏi và đủ ngắn để trở thành một nhận xét. Bạn có thể lấy nó theo cách bạn muốn, nhưng nếu bạn thổi thứ đó ra khỏi tỷ lệ, bạn sẽ thấy stack overflow là một trải nghiệm rất bực bội.
Jared Smith
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.