Làm thế nào để nối vào một tập tin trong Node?


505

Tôi đang cố gắng nối một chuỗi vào một tệp nhật ký. Tuy nhiên writeFile sẽ xóa nội dung mỗi lần trước khi viết chuỗi.

fs.writeFile('log.txt', 'Hello Node', function (err) {
  if (err) throw err;
  console.log('It\'s saved!');
}); // => message.txt erased, contains only 'Hello Node'

Bất kỳ ý tưởng làm thế nào để làm điều này một cách dễ dàng?

Câu trả lời:


799

Đối với các lần bổ sung không thường xuyên, bạn có thể sử dụng appendFile, điều này tạo ra một tệp xử lý mới mỗi lần nó được gọi là:

Không đồng bộ :

const fs = require('fs');

fs.appendFile('message.txt', 'data to append', function (err) {
  if (err) throw err;
  console.log('Saved!');
});

Đồng bộ :

const fs = require('fs');

fs.appendFileSync('message.txt', 'data to append');

Nhưng nếu bạn nối lại nhiều lần vào cùng một tệp, tốt hơn hết là sử dụng lại phần xử lý tệp .


6
Kể từ Node v0.8.0
denysonique

59
Có ai biết nếu fs.appendFile giữ một liên kết đến tệp đang mở để việc nối thêm nhanh hơn không? (thay vì mở / đóng từng ghi) nodejs.org/api/ Từ
nelsonic

7
Trong trường hợp tiện dụng: Lưu ý rằng đây là không đồng bộ. Điều này có thể dẫn đến thời gian kỳ lạ và những thứ khác. Ví dụ: nếu bạn có process.exit()ngay sau đó fs.appendFile, bạn có thể thoát trước khi đầu ra được gửi. (Sử dụng returnlà tốt.)
SilentSteel

6
Trường hợp tệ hơn, bạn có thể sử dụng phiên bản đồng bộ , appendFileSync. nodejs.org/api/ Kẻ Nhưng bạn có thể mất một trong những lợi ích tuyệt vời của Node, đó là hoạt động không đồng bộ. Hãy chắc chắn rằng bạn bắt lỗi. Có lẽ trên một số HĐH, bạn có thể bị từ chối truy cập nếu yêu cầu xử lý tệp cùng một lúc. Không chắc về việc đó.
SilentSteel

5
@chrisdew Cảm ơn bạn đã cập nhật .. nhưng ... nếu chúng ta không sử dụng câu trả lời được chấp nhận ở đây, chúng ta phải làm gì? Làm thế nào bạn giải quyết vấn đề này?
zipzit

215

Khi bạn muốn ghi vào một tệp nhật ký, tức là nối thêm dữ liệu vào cuối tệp, không bao giờ sử dụng appendFile. appendFilemở một xử lý tệp cho mỗi phần dữ liệu bạn thêm vào tệp của mình, sau một thời gian bạn sẽ gặp một EMFILElỗi đẹp .

Tôi có thể thêm rằng appendFilekhông dễ sử dụng hơn a WriteStream.

Ví dụ với appendFile:

console.log(new Date().toISOString());
[...Array(10000)].forEach( function (item,index) {
    fs.appendFile("append.txt", index+ "\n", function (err) {
        if (err) console.log(err);
    });
});
console.log(new Date().toISOString());

Lên đến 8000 trên máy tính của tôi, bạn có thể nối dữ liệu vào tệp, sau đó bạn có được điều này:

{ Error: EMFILE: too many open files, open 'C:\mypath\append.txt'
    at Error (native)
  errno: -4066,
  code: 'EMFILE',
  syscall: 'open',
  path: 'C:\\mypath\\append.txt' }

Hơn nữa, appendFilesẽ viết khi nó được kích hoạt, vì vậy nhật ký của bạn sẽ không được ghi bởi dấu thời gian. Bạn có thể kiểm tra với ví dụ, đặt 1000 thay cho 100000, thứ tự sẽ là ngẫu nhiên, tùy thuộc vào quyền truy cập vào tệp.

Nếu bạn muốn thêm vào một tệp, bạn phải sử dụng một luồng có thể ghi như thế này:

var stream = fs.createWriteStream("append.txt", {flags:'a'});
console.log(new Date().toISOString());
[...Array(10000)].forEach( function (item,index) {
    stream.write(index + "\n");
});
console.log(new Date().toISOString());
stream.end();

Bạn kết thúc nó khi bạn muốn. Bạn thậm chí không bắt buộc phải sử dụng stream.end(), tùy chọn mặc định là AutoClose:truevậy, vì vậy tệp của bạn sẽ kết thúc khi quá trình của bạn kết thúc và bạn tránh mở quá nhiều tệp.


2
Đây là vị trí trên.
Pogrindis 27/03/18

3
Cảm ơn câu trả lời tuyệt vời, nhưng tôi nghi ngờ là do tính chất không đồng bộ của Javascript, nó sẽ thực thi stream.end()trước stream.write(), vì vậy chúng tôi không nên sử dụng stream.end(), như bạn đã đề cập đó AutoClose:Truelà một tùy chọn mặc định, tại sao lại phải viết một dòng không có sử dụng.
Aashish Kumar

13
due to asynchronous nature of Javascript... Gì? Array.forEach là một hoạt động đồng bộ. JS là đồng bộ. Nó chỉ xảy ra để cung cấp một số cách để quản lý các hoạt động không đồng bộ, như Promise và async / await.
Sharcoux

1
Tôi đoán fs.appendFilekết quả là có quá nhiều tệp đang mở vì bạn thực thi nó theo cách không đồng bộ (bạn chỉ tạo không đồng bộ 10000 xử lý tệp), tôi tin rằng appendFileSyncsẽ không có vấn đề tương tự, cũng không phải fs.appendFilevới khoảng thời gian thích hợp (1s có lẽ là quá đủ) hoặc xếp hàng.
táo táo

1
@RedwolfPrograms Đối với nhật ký máy chủ bận rộn, có thể đúng. Đối với một lần mỗi nhật ký thực hiện, có thể không. Dù sao, tôi chỉ nói rằng điểm (ít nhất là lý do) trong câu trả lời này là không chính xác.
táo táo

122

Mã của bạn bằng cách sử dụng createWriteStream tạo một mô tả tệp cho mỗi lần ghi. log.end tốt hơn bởi vì nó yêu cầu nút đóng ngay sau khi ghi.

var fs = require('fs');
var logStream = fs.createWriteStream('log.txt', {flags: 'a'});
// use {flags: 'a'} to append and {flags: 'w'} to erase and write a new file
logStream.write('Initial line...');
logStream.end('this is the end line');

6
thiếu dòng đầu tiên! nên là 'var fs = Yêu cầu (' fs ');'
Stormbyte

5
Hoặc có lẽ thậm chí tốt hơn var fs = require('graceful-fs'), mà băm ra một số vấn đề được biết đến. Xem tài liệu để biết thêm.
Marko Bonaci

3
Cả dòng đầu tiên và dòng cuối đều nằm trên cùng một dòng :-p
binki

5
Xin lưu ý : Nếu bạn đang sử dụng fs.createWriteStream, sau đó sử dụng flags. Nếu bạn đang sử dụng fs.writeFilethì đó là flag. Vui lòng tham khảo Tài liệu Node JS - Hệ thống tệp để biết thêm thông tin.
Anish Nair

2
Hãy cẩn thận! Tham số không phải là "cờ" mà là "cờ" (số ít): nodejs.org/api/iêu
Benny Neugebauer

25

Ngoài ra appendFile, bạn cũng có thể truyền cờ vào writeFileđể chắp thêm dữ liệu vào tệp hiện có.

fs.writeFile('log.txt', 'Hello Node',  {'flag':'a'},  function(err) {
    if (err) {
        return console.error(err);
    }
});

Bằng cách chuyển cờ 'a', dữ liệu sẽ được thêm vào cuối tệp.


3
Xin lưu ý : Nếu bạn đang sử dụng fs.createWriteStream, sau đó sử dụng flags. Nếu bạn đang sử dụng fs.writeFilethì đó là flag. Vui lòng tham khảo Tài liệu Node JS - Hệ thống tệp để biết thêm thông tin.
Anish Nair

19

Bạn cần mở nó, sau đó viết thư cho nó.

var fs = require('fs'), str = 'string to append to file';
fs.open('filepath', 'a', 666, function( e, id ) {
  fs.write( id, 'string to append to file', null, 'utf8', function(){
    fs.close(id, function(){
      console.log('file closed');
    });
  });
});

Dưới đây là một vài liên kết sẽ giúp giải thích các tham số

mở
viết
gần


EDIT : Câu trả lời này không còn hợp lệ, hãy xem phương pháp fs.appendFile mới để nối thêm.


1
trông giống như supercobra liên tục ghi nhật ký vào tệp nhật ký, việc sử dụng fs.write không được khuyến nghị trong trường hợp này, thay vào đó hãy sử dụng fs.createWriteStream. Đọc nodejs.org/docs/v0.4.8/api/all.html#fs.write
tức giận kiwi

10
Câu trả lời không còn chính xác như của nodejs v0.4.10.
Ruben Tan

nó phải là '0666' thay vì 666.
Ya Zhuang

13

Node.js 0.8 có fs.appendFile:

fs.appendFile('message.txt', 'data to append', (err) => {
  if (err) throw err;
  console.log('The "data to append" was appended to file!');
});

Tài liệu


3
fd = fs.openSync(path.join(process.cwd(), 'log.txt'), 'a')
fs.writeSync(fd, 'contents to append')
fs.closeSync(fd)

5
bất cứ điều gì đồng bộ hóa () hầu như luôn là một ý tưởng tồi trừ khi bạn chắc chắn 100% rằng bạn hoàn toàn CẦN nó. Thậm chí sau đó, có lẽ bạn đang làm sai.
Zane Claes

4
Điều đó không có nghĩa là nó sai. Nó chỉ làm nó đồng bộ. Có thể không phải là cách thực hành tốt nhất cho Node.js, nhưng nó được hỗ trợ.
Luis R.

2
Tôi đã sử dụng "ur doin it nhầm" theo nghĩa thông thường của cụm từ internet. Rõ ràng là nó được hỗ trợ = P
Zane Claes

7
Đồng ý với async, nhưng đôi khi nếu bạn chỉ viết một kịch bản tương tác, đồng bộ hóa vẫn ổn.
bryanmac

7
Viết đồng bộ là hoàn toàn ok nếu bạn đang làm một ứng dụng dòng lệnh người dùng (ví dụ: script để làm một số thứ). Đó là cách nhanh hơn để làm công cụ. Tại sao nút có phương thức đồng bộ nếu không cho mục đích này?
Jan ęwięcki

3

Nếu bạn muốn một cách dễ dàng và không căng thẳng để viết nhật ký từng dòng trong một tệp, thì tôi khuyên bạn nên fs-thêm :

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

const file = 'logfile.txt';
const options = {flag: 'a'};

async function writeToFile(text) {
  await fs.outputFile(file, `${text}${os.EOL}`, options);
}

writeToFile('First line');
writeToFile('Second line');
writeToFile('Third line');
writeToFile('Fourth line');
writeToFile('Fifth line');

Đã thử nghiệm với Node v8.9.4.


2

Cách tiếp cận của tôi khá đặc biệt. Về cơ bản tôi sử dụng WriteStreamgiải pháp nhưng không thực sự 'đóng' fd bằng cách sử dụng stream.end(). Thay vào đó tôi sử dụng cork/ uncork. Điều này có lợi ích của việc sử dụng RAM thấp (nếu điều đó quan trọng với bất kỳ ai) và tôi tin rằng việc sử dụng để ghi / ghi lại (trường hợp sử dụng ban đầu của tôi) sẽ an toàn hơn.

Sau đây là một ví dụ khá đơn giản. Lưu ý rằng tôi vừa thêm một forvòng lặp giả để giới thiệu - trong mã sản xuất tôi đang chờ tin nhắn websocket.

var stream = fs.createWriteStream("log.txt", {flags:'a'});
for(true) {
  stream.cork();
  stream.write("some content to log");
  process.nextTick(() => stream.uncork());
}

uncork sẽ xóa dữ liệu vào tập tin trong tích tắc tiếp theo.

Trong kịch bản của tôi, có các đỉnh lên tới ~ 200 ghi mỗi giây với nhiều kích cỡ khác nhau. Tuy nhiên, vào ban đêm, chỉ cần một ít viết mỗi phút. Mã này đang hoạt động siêu đáng tin cậy ngay cả trong thời gian cao điểm.


1

Tôi chỉ đưa ra đề xuất này vì việc kiểm soát cờ mở đôi khi rất hữu ích, ví dụ, bạn có thể muốn cắt bớt tệp đó trước và sau đó nối thêm một loạt ghi vào nó - trong trường hợp đó sử dụng cờ 'w' khi mở tệp và không đóng nó cho đến khi tất cả các bài viết được thực hiện. Tất nhiên appendFile có thể là những gì bạn đang theo đuổi :-)

  fs.open('log.txt', 'a', function(err, log) {
    if (err) throw err;
    fs.writeFile(log, 'Hello Node', function (err) {
      if (err) throw err;
      fs.close(log, function(err) {
        if (err) throw err;
        console.log('It\'s saved!');
      });
    });
  });


1

Sử dụng fs.appendFilehoặc fsPromises.appendFilelà các tùy chọn nhanh nhất và mạnh mẽ nhất khi bạn cần nối một cái gì đó vào một tệp.

Ngược lại với một số câu trả lời được đề xuất, nếu đường dẫn tệp được cung cấp cho appendFilehàm, nó thực sự tự đóng . Chỉ khi bạn vượt qua trong một tập tin mà bạn nhận được bằng một cái gì đó giống như fs.open()bạn phải chăm sóc đóng nó.

Tôi đã thử nó với hơn 50.000 dòng trong một tập tin.

Ví dụ:

(async () => {
  // using appendFile.
  const fsp = require('fs').promises;
  await fsp.appendFile(
    '/path/to/file', '\r\nHello world.'
  );

  // using apickfs; handles error and edge cases better.
  const apickFileStorage = require('apickfs');
  await apickFileStorage.writeLines(
    '/path/to/directory/', 'filename', 'Hello world.'
  );
})();

nhập mô tả hình ảnh ở đây

Tham chiếu: https://github.com/nodejs/node/issues/7560
Thực thi ví dụ: https://github.com/apickjs/apickFS/blob/master/writeLines.js


0

Đây là một kịch bản đầy đủ. Điền vào tên tệp của bạn và chạy nó và nó sẽ hoạt động! Đây là một video hướng dẫn về logic đằng sau kịch bản.

var fs = require('fs');

function ReadAppend(file, appendFile){
  fs.readFile(appendFile, function (err, data) {
    if (err) throw err;
    console.log('File was read');

    fs.appendFile(file, data, function (err) {
      if (err) throw err;
      console.log('The "data to append" was appended to file!');

    });
  });
}
// edit this with your file names
file = 'name_of_main_file.csv';
appendFile = 'name_of_second_file_to_combine.csv';
ReadAppend(file, appendFile);

0

một cách dễ dàng hơn để làm điều này là

const fs = require('fs');
fs.appendFileSync('file.txt', 'message to append into file');

Dễ hơn cái gì? Bạn đã kiểm tra câu trả lời hàng đầu, đưa ra chính xác cùng một appendFileSyncgiải pháp?
Dan Dascalescu

0
const inovioLogger = (logger = "") => {
    const log_file = fs.createWriteStream(__dirname + `/../../inoviopay-${new Date().toISOString().slice(0, 10)}.log`, { flags: 'a' });
    const log_stdout = process.stdout;
    log_file.write(logger + '\n');
}

0

Ngoài câu trả lời của denysonique , đôi khi loại không đồng bộ appendFilevà các phương thức không đồng bộ khác trong NodeJS được sử dụng khi trả lại lời hứa thay vì gọi lại. Để làm điều đó, bạn cần bọc hàm bằng promisifyHOF hoặc nhập các hàm async từ không gian tên hứa hẹn:

const { appendFile } = require('fs').promises;

await appendFile('path/to/file/to/append', dataToAppend, optionalOptions);

Tôi hy vọng nó sẽ giúp

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.