我正在尝试为Node.js实现一个例程,该例程将允许打开一个文件,该文件此时正由其他进程附加,然后在将数据附加到文件时立即返回大块数据。可以认为它与tail -f
UNIX命令相似,但是可以立即使用,只要有可用的块即可,而不是轮询一段时间内的更改。另外,您可以将其视为与使用套接字一样处理文件—期望on('data')
会不时触发,直到明确关闭文件为止。
在C land中,如果要实现此功能,则只需打开文件,将其文件描述符输入select()
(或任何具有类似名称的替代功能),然后在文件描述符标记为“可读”时读取块。因此,当没有什么要读取的内容时,它将无法读取,而当文件中附加了某些内容时,它将再次变得可读。
对于Javascript中的以下代码示例,我有些期待这种行为:
function readThatFile(filename) {
const stream = fs.createReadStream(filename, {
flags: 'r',
encoding: 'utf8',
autoClose: false // I thought this would prevent file closing on EOF too
});
stream.on('error', function(err) {
// handle error
});
stream.on('open', function(fd) {
// save fd, so I can close it later
});
stream.on('data', function(chunk) {
// process chunk
// fs.close() if I no longer need this file
});
}
但是,此代码示例仅会在遇到EOF时解救,因此我不能等待新的块到达。当然,我可以使用
fs.open
和fs.read
重新实现此功能,但这在某种程度上违背了Node.js
的目的。另外,我可以通过fs.watch()
文件进行更改,但是它不能通过网络运行,而且我不喜欢一直重新打开文件而不只是保持打开状态的想法。我试图做到这一点:
const fd = fs.openSync(filename, 'r'); // sync for readability' sake
const stream = net.Socket({ fd: fd, readable: true, writable: false });
但是没有运气–
net.Socket
不高兴,并抛出TypeError: Unsupported fd type: FILE
。那么,有什么解决方案吗?
UPD:这是不可能的,my answer解释了原因。
最佳答案
我没有研究文件读取流的内部结构,但有可能它们不支持等待文件写入更多数据。但是,fs
包绝对支持其最基本的功能。
为了解释拖尾的工作方式,我编写了一个有点古怪的tail
函数,该函数将读取整个文件并为每行调用一个回调(仅由\n
分隔),然后等待文件中写入更多行。请注意,一种更有效的方法是拥有固定大小的行缓冲区,然后将字节随机混入其中(对于极长的行有特殊情况),而不是修改JavaScript字符串。
var fs = require('fs');
function tail(path, callback) {
var descriptor, bytes = 0, buffer = new Buffer(256), line = '';
function parse(err, bytesRead, buffer) {
if (err) {
callback(err, null);
return;
}
// Keep track of the bytes we have consumed already.
bytes += bytesRead;
// Combine the buffered line with the new string data.
line += buffer.toString('utf-8', 0, bytesRead);
var i = 0, j;
while ((j = line.indexOf('\n', i)) != -1) {
// Callback with a single line at a time.
callback(null, line.substring(i, j));
// Skip the newline character.
i = j + 1;
}
// Only keep the unparsed string contents for next iteration.
line = line.substr(i);
// Keep reading in the next tick (avoids CPU hogging).
process.nextTick(read);
}
function read() {
var stat = fs.fstatSync(descriptor);
if (stat.size <= bytes) {
// We're currently at the end of the file. Check again in 500 ms.
setTimeout(read, 500);
return;
}
fs.read(descriptor, buffer, 0, buffer.length, bytes, parse);
}
fs.open(path, 'r', function (err, fd) {
if (err) {
callback(err, null);
} else {
descriptor = fd;
read();
}
});
return {close: function close(callback) {
fs.close(descriptor, callback);
}};
}
// This will tail the system log on a Mac.
var t = tail('/var/log/system.log', function (err, line) {
console.log(err, line);
});
// Unceremoniously close the file handle after one minute.
setTimeout(t.close, 60000);
综上所述,您还应该尝试利用NPM社区。经过一些搜索,我发现了tail-stream包,它可以通过流执行您想要的操作。