如何在Node中向文件追加内容?

761

我试图将一个字符串追加到日志文件中。然而,writeFile 每次写入字符串之前都会擦除文件中的内容。

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

有没有简单的方法来做到这一点?

18个回答

1150

对于偶尔的附加操作,您可以使用appendFile,它每次被调用时都会创建一个新的文件句柄:

异步方式

const fs = require('fs');

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

同步地:

const fs = require('fs');

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

但是如果你反复将内容追加到同一个文件中,最好是重用文件句柄


76
有人知道 fs.appendFile 是否会保持对文件的链接以便进行快速追加操作吗?(而不是每次写入时打开/关闭)http://nodejs.org/api/fs.html#fs_fs_appendfile_filename_data_encoding_utf8_callback - nelsonic
6
根据源代码,没有特别的处理。 - Maël Nison
13
如果有用的话:请注意这是异步操作,这可能导致奇怪的时间问题和其他问题。例如:如果在 fs.appendFile 后面紧接着使用了 process.exit(),则可能在输出被发送之前就退出了。(使用 return 没问题)。 - SilentSteel
9
在最坏情况下,您可以使用同步版本的appendFileSync。 http://nodejs.org/api/fs.html#fs_fs_appendfilesync_filename_data_options 但是您可能会失去Node的一个重要好处,即异步操作。确保捕获错误。也许在某些操作系统上,如果同时请求文件句柄,您可能会遇到访问被拒绝的情况。对此不太确定。 - SilentSteel
7
谢谢您的更新。但是,如果我们不使用接受的答案,我们应该怎么办?您是如何解决这个困境的? - zipzit
显示剩余8条评论

401

当你想要写入日志文件时,即将数据追加到文件末尾时,请绝不使用appendFileappendFile为每个要添加到文件的数据打开一个文件句柄,在一段时间后,您会得到一个漂亮的EMFILE错误。

我可以补充说,appendFile并不比WriteStream更易于使用。

使用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());

在我的电脑上,文件可以增加数据,这样你就能获得以下结果:

{ 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' }
此外,appendFile 在启用时会进行写入,因此您的日志将不会按时间戳编写。您可以尝试使用示例,在100000位置处替换为1000,顺序将是随机的,这取决于对文件的访问。
如果您想要追加文件,您必须像这样使用可写流:
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();

你可以在任何时候结束它。甚至不需要使用 stream.end(),默认选项是 AutoClose:true,因此当进程结束时文件将自动结束,避免打开过多的文件。


6
谢谢您的回复,但我的疑问是由于 JavaScript 的异步特性,它将在执行 stream.write() 之前执行 stream.end(),因此我们不应该使用 stream.end()。另外,正如您提到的,“AutoClose: True” 是默认选项,那么为什么要写一行没有用处的代码呢? - Aashish Kumar
31
由于 JavaScript 的异步特性是,什么?Array.forEach 是同步操作。JS 是同步的。它只是提供了一些管理异步操作的方式,比如 Promises 和 async/await。 - Sharcoux
9
我猜测 fs.appendFile 的结果会导致太多打开的文件,因为您是以异步方式执行它(您只是异步地创建了10000个文件句柄),我相信 appendFileSync 不会有类似的问题,也不会出现使用适当间隔(1秒可能已经足够)或排队的 fs.appendFile 问题。 - apple apple
2
@appleapple 但你仍然每次都要打开文件。对于日志来说,保持它处于打开状态更有意义。 - Radvylf Programs
4
对于繁忙的服务器日志,可能是正确的。对于一次性执行日志,可能不是。无论如何,我只是想声明这个答案中的观点(至少原因)是不正确的。 - apple apple
4
对于日志记录,我建议使用fs.appendFileSync(它可以多次运行而不会出现问题)。如果脚本在使用stream.writefs.appendFile记录日志后崩溃或退出,那么很可能你无法获取已记录的日志(这些日志通常是为了调试崩溃/退出所必需的)。 - Dan

160

你的代码使用createWriteStream为每个写操作创建一个文件描述符。而log.end更好,因为它会要求Node在写操作后立即关闭。

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
抱歉,我是一名英文语言模型,虽然我可以理解和翻译中文,但我的专业领域是英文。如果您需要的话,我可以尝试将下面的内容翻译成英文:缺失第一行!应该是“var fs = require('fs');”。 - Stormbytes
6
或者更好的选择是 var fs = require('graceful-fs'),它解决了一些已知的问题。有关更多信息,请参见文档 - Marko Bonaci
3
初始行和结束行虽然在同一行上,但是 :-p - binki
8
请注意:如果您使用的是 fs.createWriteStream,那么请使用 flags 参数。如果您使用的是 fs.writeFile,则需要使用 flag 参数。有关更多信息,请参阅Node.js 文件系统文档 - Anish Nair
2
小心!参数不是“flags”,而是“flag”(单数):https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback - Benny Code
4
你使用了旗帜是正确的,但你本身是错误的。在你之前几个月已经发布过了。你链接到了fs.writeFile文档,它确实使用了“flag”。但是这个解决方案使用了fs.createWriteStream,并且参数“flags”是正确的-https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options - Qwiso

42

使用 a+ 标志来追加和创建文件(如果不存在):

fs.writeFile('log.txt', 'Hello Node', { flag: "a+" }, (err) => {
  if (err) throw err;
  console.log('The file is created if not existing!!');
}); 

文档:https://nodejs.org/api/fs.html#fs_file_system_flags


37

除了appendFile,你也可以在writeFile中传递一个标志来将数据追加到现有文件中。

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

通过传递标志 'a',数据将被追加到文件末尾。


5
请注意:如果您使用的是 fs.createWriteStream,则需要使用 flags。如果您使用的是 fs.writeFile,则需要使用 flag。请参考 Node JS Docs - File System 获取更多信息。 - Anish Nair

27
你需要打开它,然后写入内容。
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');
    });
  });
});

以下链接可以帮助解释这些参数:

打开文件
写入文件
关闭文件


编辑:此答案已不再有效,请查看新的fs.appendFile方法来进行追加。


1
看起来supercobra不断地将日志写入日志文件,不建议在这种情况下使用fs.write,应该使用fs.createWriteStream。请阅读http://nodejs.org/docs/v0.4.8/api/all.html#fs.write。 - angry kiwi
11
随着Node.js v0.4.10的发布,这个答案不再准确。 - Ruben Tan
应将其翻译为“0666”,而不是“666”。 - Ya Zhuang

17

我的方法非常特别。我基本上使用WriteStream解决方案,但不实际通过使用stream.end()来“关闭”fd。而是使用cork/uncork。如果有人关心的话,这样能够低内存占用,并且我认为它更安全地用于日志记录(我的原始用例)。

以下是一个相当简单的示例。请注意,我只添加了一个伪for循环进行演示--在生产代码中,我正在等待websocket消息。

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

uncork将在下一个时钟周期将数据刷新到文件中。

在我的情况下,有各种尺寸的高达每秒 ~200 写入的峰值。然而,在夜间每分钟只需要少数写入。即使在高峰期,该代码也非常可靠。


17

使用 fs.appendFile 或者 fsPromises.appendFile 是在需要向文件追加内容时最快速和最健壮的选项。

与某些答案建议的情况相反,如果将文件路径提供给 appendFile 函数,则它实际上会自动关闭。只有当您通过类似于 fs.open() 获取的文件句柄传递时,您必须注意关闭它。

我曾尝试在一个包含超过 50,000 行的文件中使用它。

示例:

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

输入图像描述

参考: https://github.com/nodejs/node/issues/7560


你为什么要从GitHub上删除你的关联项目apickfs? - Petr Bugyík

14

Node.js 0.8有fs.appendFile

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

文档


7
如果你想要以一种简单且无压力的方式逐行在文件中写入日志,那么我推荐使用fs-extra
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');

使用Node v8.9.4进行测试。


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