Как НЕ прекратить чтение файла при встрече с EOF?
Я пытаюсь реализовать подпрограмму для Node.js, которая позволила бы открыть файл, к которому в данный момент добавляется какой-то другой процесс, а затем сразу же возвращать порции данных, когда они добавляются в файл. Считается, что она похожа на tail -f
UNIX tail -f
, однако действует немедленно, когда доступны фрагменты, а не опрашивает изменения во времени. В качестве альтернативы, вы можете думать об этом как о работе с файлом, так же как и с сокетом - ожидая, что время 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
, но это несколько fs.read
цели Node.js
В качестве альтернативы я мог бы fs.watch()
файл 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
.
Итак, какие-либо решения?
Ответы
Ответ 1
Я не изучал внутренности потоков чтения для файлов, но возможно, что они не поддерживают ожидание того, что файл будет иметь больше данных, написанных на нем. Однако пакет 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 пакет, который может делать то, что вы хотите, с потоками.
Ответ 2
Предыдущие ответы упоминали tail-stream подход, который использует fs.watch, fs.read и fs.stat вместе, чтобы создать эффект потоковой передачи содержимое файла. Вы можете увидеть этот код в действии здесь.
Другим, возможно, хакерским подходом может быть просто использование хвоста путем нереста дочернего процесса с ним. Это, конечно же, связано с ограничением, что хвост должен существовать на целевой платформе, но одна из сильных сторон node использует его для разработки асинхронных систем через spawn и даже в окнах, вы можете выполнить node в альтернативной оболочке, например, msysgit или cygwin, чтобы получить доступ к утилите хвоста.
Код для этого:
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);
}
);
Ответ 3
То, что вы пытаетесь сделать, - это файл FIFO (аббревиатура для First In First Out), который, как вы сказали, работает как сокет.
Там есть node.js модуль, который позволяет вам работать с файлами fifo.
Я не знаю, для чего вам это нужно, но есть лучшие способы работы с сокетами на node.js. Вместо этого попробуйте socket.io.
Вы также можете посмотреть на этот предыдущий вопрос:
Чтение файла в режиме реального времени с помощью node.js
Обновление 1
Я не знаком ни с одним модулем, который будет делать то, что вы хотите, с обычным файлом, а не с сокетным типом. Но, как вы сказали, вы можете использовать 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);
});
Затем из командной строки попробуйте echo someline > somefile.txt
и посмотрите на консоли.
Вы также можете взглянуть на это: https://github.com/layerssss/node-tailer