Linux — одна из самых прекрасных разработок на сегодняшний день, и иногда мне интересно, как работает та или иная команда в linux под капотом, например, как именно работает «ls». Поэтому я попытался воспроизвести одну из самых используемых команд linux, «Tail -f» в nodejs.
Для тех, кто не знает, «Tail -f» печатает последние 10 строк из файла, а затем отслеживает обновления в файле и печатает содержимое, когда файл обновляется. Теперь node js имеет встроенный модуль файловой системы, который помогает нам играть с файлами и папками, и есть несколько прямых методов, доступных для чтения файла, мониторинга файла, записи файла. Звучит просто — взять эти команды и использовать их одну за другой, но это не так просто.
Есть вещи, с которыми мы должны справиться:
Мы должны получать данные при каждом обновлении файла
Мы не должны читать весь файл каждый раз, когда он обновляется.
Поэтому я начал просматривать все методы, доступные в пакете fs в nodejs. Я получил readline, который читает файл строка за строкой и на каждой строке выдает ложные данные в событии
// Read file line by line
var readline = require('readline');
var rl = readline.createInterface(process.stdin, process.stdout);
rl.on('line', (log) => {
console.log(`Log is: ${log}`);
rl.close();
});
Этот метод выглядел очень полезным для моего случая, поэтому я начал с него, но дело в том, что теперь мне нужно решить, когда я должен вызывать этот метод. Мне нужно следить за обновлением файла, поэтому я искал любой другой пакет, который может обнаружить изменения в файле.
Я нашел fs.watchfile, который выдает событие всякий раз, когда в файле происходят какие-либо изменения. Я попробовал этот метод, но заметил, что он выдает события при обновлении метаданных, поэтому мне пришлось добавить некоторый фильтр. Мы получили текущую и предыдущую статистику по каждому событию. Теперь я должен сравнить размер файла, чтобы увидеть, обновлены ли фактические данные в файле или нет, и выполнять действия только при изменении размера файла.
// Keep a watch on file value change
fs.watchFile(filename, (curr, prev) => {
console.log("Previous Stats", prev);
console.log("Current Stats", curr);
if (curr.size.valueOf() === prev.size.valueOf())
return;
});
Чтобы быть вдвойне уверенным, я должен сохранить текущий размер для последующего использования, а затем сравнить его со следующим размером. Теперь я могу следить за обновлением, поэтому при каждом обновлении я вызываю метод readline.
Теперь у меня возникла еще одна большая проблема: при каждом событии я считывал и передавал полный файл. Это было решено просто: я сохраняю указатель и перемещаю его на последнюю строку, а когда я снова читаю файл, я печатаю только ту строку, которая находится после последнего указателя.
// Read file line by line
let pointer = 0;
// Keep a watch on file value change
fs.watchFile(filename, (curr, prev) => {
if (curr.size.valueOf() === prev.size.valueOf())
return;
let currentpointer = 0;
var readline = require('readline');
var rl = readline.createInterface(process.stdin, process.stdout);
rl.on('line', (log) => {
currentpointer++
if (currentpointer > pointer)
console.log(`Log is: ${log}`);
rl.close();
});
pointer = currentpointer;
});
Это работало так, как нужно, но оставалась проблема: это было неэффективно, поскольку, хотя я не печатал строку журнала каждый раз, когда происходило событие, я просматривал каждую строку, а это отнимало время и память.
Поэтому я начал искать альтернативу readline, которая может читать определенную строку из файла. Я нашел простой метод «read», в котором я могу передать, с какой части я могу читать, но я должен передать начальный байт, а не строку. Я использовал это, теперь вместо чтения строк я читал буфер и изменил указатель со строки на байт буфера.
Теперь я должен открыть файл и прочитать его с последнего байта, который я прочитал в последнем событии. Теперь вместо строковых данных у меня был буфер, поэтому я преобразовал их в обычную строку из буфера, затем разделил строку на «n», что является новой строкой, а затем вывел элементы массива один за другим.
// Keep a watch on file value change
fs.watchFile(filename, (curr, prev) => {
if (filename) {
//Check if file is actually updated
if (curr.size.valueOf() === previousFileSize.valueOf())
return;
let buffer = new Buffer.alloc(curr.size - lastReadByte + 1);
previousFileSize = curr.size;
console.log(`${filename} file Changed`);
fs.open(filename, fileOpenMode, (err, filedata) => {
if (err)
return console.error(err);
console.log("Reading the file");
fs.read(filedata, buffer, 0, buffer.length, lastReadByte, (err, bytes) => {
if (err)
return console.error(err);
if (bytes > 0) {
const dataString = buffer.slice(0, bytes).toString();
const dataArray = dataString.split("n");
dataArray.forEach(logline => {
if (logline)
console.log(logline)
});
}
lastReadByte = stats.size
// Close the opened file.
fs.close(filedata, (err) => {
if (err)
return console.error(err);
console.log("File closed successfully");
});
});
});
}
});
Таким образом, это эффективный способ передачи постоянно обновляющегося файла, реализованный в Node JS. Счастливого кодинга!!! Для получения большего количества материалов, вы можете подписаться на мой канал YT