如何结合smb2与read-last-line用Node.js读取共享文件最后N行?
问题描述
我有两段Node.js代码:一段使用smb2库读取SMB共享文件,代码如下:
// load the library var SMB2 = require('smb2'); // create an SMB2 instance var smb2Client = new SMB2({ share:'\\\\192.168.0.111\\folder' , domain:'WORKGROUP' , username:'username' , password:'password' }); // read the file smb2Client.readFile('path\\to\\the\\file.txt', "utf-8", function(error, data){ if(error) throw error; console.log(data); });
另一段使用read-last-line库读取本地文件的最后20行,代码如下:
// load the library const readLastLine = require('read-last-line'); // read the file readLastLine.read('path\\to\\the\\file.txt', 20).then(function (lines) { console.log(lines) }).catch(function (err) { console.log(err.message); });
我不知道如何将这两段代码结合以实现读取共享大型文件最后N行的需求,请问有什么解决建议?
解决方案
read-last-line依赖本地文件系统API,无法直接处理SMB共享文件,直接结合两段代码行不通。针对大型SMB文件,推荐以下两种高效方案:
方法一:流式反向读取(适合中等大小文件)
利用smb2的createReadStream从文件末尾分段读取,反向解析换行符,无需加载整个文件到内存:
const SMB2 = require('smb2'); const smb2Client = new SMB2({ share: '\\\\192.168.0.111\\folder', domain: 'WORKGROUP', username: 'username', password: 'password' }); function readLastNLinesSMB(filePath, targetLines) { return new Promise((resolve, reject) => { let collectedLines = []; let remainingBuffer = ''; let foundLines = 0; let stopReading = false; // 先获取文件总大小 smb2Client.stat(filePath, (statErr, stats) => { if (statErr) return reject(statErr); let currentStartPos = Math.max(0, stats.size - 1024); // 初始从末尾前1KB开始读 function readNextChunk() { if (currentStartPos < 0 || stopReading) { // 处理剩余未分割的内容 if (remainingBuffer.trim()) collectedLines.unshift(remainingBuffer.trim()); resolve(collectedLines.slice(0, targetLines)); return; } const chunkEndPos = Math.min(stats.size - 1, currentStartPos + 1024 - 1); const stream = smb2Client.createReadStream(filePath, { start: currentStartPos, end: chunkEndPos }); stream.on('data', (chunk) => { remainingBuffer = chunk.toString('utf-8') + remainingBuffer; const splitParts = remainingBuffer.split('\n'); // 从后往前遍历分割结果,收集有效行 for (let i = splitParts.length - 1; i >= 0 && !stopReading; i--) { const line = splitParts[i].trim(); if (line) { collectedLines.unshift(line); foundLines++; if (foundLines >= targetLines) { stopReading = true; stream.destroy(); break; } } } // 更新剩余未处理的buffer(未收集够行数时) if (!stopReading) { remainingBuffer = splitParts[0] || ''; } }); stream.on('end', () => { currentStartPos -= 1024; if (!stopReading) readNextChunk(); }); stream.on('error', reject); } readNextChunk(); }); }); } // 使用示例 readLastNLinesSMB('path\\to\\the\\file.txt', 20) .then(lines => console.log(lines)) .catch(err => console.error(err));
方法二:文件指针精准定位(适合超大型文件)
通过smb2底层的open/seek/read方法,从文件末尾逐字节向前读取,统计换行符,内存占用极低:
const SMB2 = require('smb2'); const smb2Client = new SMB2({ share: '\\\\192.168.0.111\\folder', domain: 'WORKGROUP', username: 'username', password: 'password' }); function readLastNLinesSMB(filePath, targetLines) { return new Promise((resolve, reject) => { let collectedLines = []; let currentByteCount = 0; let currentPosition = 0; let fileDescriptor = null; // 打开目标文件 smb2Client.open(filePath, 'r', (openErr, fd) => { if (openErr) return reject(openErr); fileDescriptor = fd; // 获取文件大小 smb2Client.fstat(fileDescriptor, (statErr, stats) => { if (statErr) return cleanupAndReject(statErr); currentPosition = stats.size - 1; function readSingleByte() { // 终止条件:读到文件开头或收集够目标行数 if (currentPosition < 0 || collectedLines.length >= targetLines) { if (currentByteCount > 0) { // 读取最后剩余的内容作为第一行 const lineBuffer = Buffer.alloc(currentByteCount); smb2Client.read(fileDescriptor, lineBuffer, 0, currentByteCount, 0, (readErr) => { if (readErr) return cleanupAndReject(readErr); collectedLines.unshift(lineBuffer.toString('utf-8').trim()); cleanupAndResolve(collectedLines.slice(0, targetLines)); }); } else { cleanupAndResolve(collectedLines.slice(0, targetLines)); } return; } const byteBuffer = Buffer.alloc(1); smb2Client.read(fileDescriptor, byteBuffer, 0, 1, currentPosition, (readErr, bytesRead) => { if (readErr) return cleanupAndReject(readErr); if (bytesRead === 0) return cleanupAndResolve(collectedLines.slice(0, targetLines)); const char = byteBuffer.toString('utf-8'); if (char === '\n') { if (currentByteCount > 0) { // 读取当前行内容 const lineBuffer = Buffer.alloc(currentByteCount); smb2Client.read(fileDescriptor, lineBuffer, 0, currentByteCount, currentPosition + 1, (lineReadErr) => { if (lineReadErr) return cleanupAndReject(lineReadErr); collectedLines.unshift(lineBuffer.toString('utf-8').trim()); if (collectedLines.length >= targetLines) { cleanupAndResolve(collectedLines.slice(0, targetLines)); } else { currentByteCount = 0; currentPosition--; readSingleByte(); } }); } else { // 跳过连续换行符 currentPosition--; readSingleByte(); } } else { // 累计当前行的字节数 currentByteCount++; currentPosition--; readSingleByte(); } }); } readSingleByte(); }); }); // 清理资源并返回结果 function cleanupAndResolve(result) { if (fileDescriptor) smb2Client.close(fileDescriptor, () => {}); resolve(result); } // 清理资源并返回错误 function cleanupAndReject(err) { if (fileDescriptor) smb2Client.close(fileDescriptor, () => {}); reject(err); } }); } // 使用示例 readLastNLinesSMB('path\\to\\the\\file.txt', 20) .then(lines => console.log(lines)) .catch(err => console.error(err));
注意事项
- 确保使用最新稳定版的
smb2库,避免API兼容性问题。 - 若文件编码不是UTF-8,需调整
toString方法的编码参数(如'gbk')。 - 若共享文件存在并发写入操作,读取结果可能不一致,需根据业务场景添加同步机制。
内容的提问来源于stack exchange,提问作者Mohamed Hedi
相关产品推荐
相关产品推荐

