在满足EOF时如何不停止阅读文件?

时间:2015-04-28 15:12:00

标签: javascript node.js file-io

我正在尝试为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.openfs.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

那么,有什么解决方案吗?

4 个答案:

答案 0 :(得分:0)

我没有查看文件读取流的内部,但是它们可能不支持等待文件写入更多数据。但是,fs包肯定支持它的最基本功能。

为了解释尾部如何工作,我编写了一个有点hacky 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包可以做你想要的,用流。

答案 1 :(得分:0)

之前的答案提到tail-stream的方法,它使用fs.watch,fs.read和fs.stat一起创建流式传输文件内容的效果。您可以在行动here中看到该代码。

另一种,也许更黑客的方法可能是通过使用它生成子进程来使用tail。这当然伴随着tail必须存在于目标平台上的限制,但节点的优势之一是使用它来通过spawn甚至在windows上进行异步系统开发,你可以在像msysgit或cygwin这样的备用shell中执行节点来获取访问尾部实用程序。

此代码:

var spawn = require('child_process').spawn;

var child = spawn('tail',
    ['-f', 'my.log']);

child.stdout.on('data',
    function (data) {
        console.log('tail output: ' + data);
    }
);

child.stderr.on('data',
    function (data) {
        console.log('err data: ' + data);
    }
);

答案 2 :(得分:0)

因此,似乎人们已经在寻找这个问题的答案已有五年了,而关于该主题的答案还没有。

简而言之:您不能。尤其是在Node.js中,您根本无法。

长答案:没有什么理由。

首先,在这方面,POSIX标准clarifies select() behavior如下:

与常规文件关联的文件描述符应始终为true以便准备读取,准备写入以及出现错误情况。

因此,select()无助于检测超出文件末尾的写入。

使用poll() it's similar

常规文件应始终以TRUE进行读写。

我不能肯定地使用epoll(),因为它不是标准的,您必须阅读quite lengthy implementation,但我认为它是相似的。

由于libuv是Node.js实现的核心,因此它使用read(), pread() and preadv() in its uv__fs_read(),在文件末尾调用时两者都不会阻塞,因此遇到EOF时,它将始终返回空缓冲区。所以,这里也没有运气。

因此,总结一下,如果需要这种功能,那么您的设计肯定有问题,应该对其进行修改。

答案 3 :(得分:-1)

你要做的是一个FIFO文件(先入先出的首字母缩写),正如你所说的那样就像一个插座。

node.js module that allows you to work with fifo个文件。

我不知道你想要什么,但有更好的方法来处理node.js上的套接字。请改为socket.io

您还可以查看上一个问题: Reading a file in real-time using Node.js

更新1

我不熟悉使用常规文件而不是套接字类型的任何模块。但正如你所说,你可以使用$c = 0; foreach ($sale['fm'] as $key => $value) { //Here you have the related tax $tax = $taxes[$c]; $i=0; $g=count($value['base']); while($i < $g) { echo($sale['fm'][$key]['base'][$i]."<br />"); $i++; } $c++; } 来做这个伎俩:

tail -f

然后从命令行尝试// filename must exist at the time of running the script var filename = 'somefile.txt'; var spawn = require('child_process').spawn; var tail = spawn('tail', ['-f', filename]); tail.stdout.on('data', function (data) { data = data.toString().replace(/^[\s]+/i,'').replace(/[\s]+$/i,''); console.log(data); }); 并在控制台上观看。

您可能还希望看一下:https://github.com/layerssss/node-tailer