You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何结合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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 21:01:04