使用child_process处理带有回车符(\r)的标准输出流

7
我正在编写一个相对简单的应用程序,允许内部系统通过REST调用请求从远程服务器到另一个远程服务器的复制过程(使用rsync)。
我已经熟悉了express框架,并开始尝试使用child_process库,但遇到了一个小问题。
我成功地使用node的childProcess.spawn()启动了rsync进程,但我的问题是,rsync将其进度行输出为带有回车符(\r)而不是换行符(\n)的缓冲区。因此STDOUT事件(process.stdout.on('data', {}))仅在设置传输之前和复制完成后被调用一次,因为在回车符上不会刷新STDOUT数据,只有在作业完成时才会有进度更新和换行符。
最新版本的rsync(3.1.0)中有一个开关,可以将输出缓冲区结尾改为\n而不是\r,但很遗憾,我所在的公司将不会在很长一段时间内采用这个版本。
我按照通常的方式生成和读取child_process...
var doCopy = function (onFinish) {
    var process = childProcess.spawn('ssh', [
        source.user + "@" + source.host,
        "rsync",
        "-avz",
        "--progress",
        source.path + source.file,
        "-e ssh",
        dest.user + "@" + dest.host + ":" + dest.path
    ]);

    process.on('error', function (error) {
        console.log("ERR: " + error.code);
    })

    process.stdout.on('data', function (data) {
        console.log("OUT: " + data);
    });

    process.stderr.on('data', function (data) {
        console.log("ERR: " + data);
    });

    process.on('close', function (code) {
        console.log("FIN: " + code);
        if(onFinish){
            onFinish(code);
        }
    });
}

......控制台输出.......

OUT: building file list ... 
OUT: 
1 file to consider

OUT: test4.mp4

         32,768   0%    0.00kB/s    0:00:00  
    169,738,240  32%  161.84MB/s    0:00:02  
    338,165,760  64%  161.32MB/s    0:00:01  
    504,692,736  96%  160.53MB/s    0:00:00  
    524,288,000 100%  160.35MB/s    0:00:03 (xfr#1, to-chk=0/1)

OUT: 
sent 509,959 bytes  received 46 bytes  113,334.44 bytes/sec
total size is 524,288,000  speedup is 1,028.01

FIN: 0

所以你可以看到,当rsync输出新的一行(出现“OUT:”时)才会调用stdout.on('data')。
我的问题是,我能改变这个吗?也许将流通过转换使其在出现\r时刷新?然后我可以使用正则表达式对该行进行匹配并再次提供进度更新。
如果失败,我想我的另一个选择就是生成另一个进程来监视不断增长的文件?
非常感谢任何帮助/建议。
2个回答

3
我找到了一个非常好的模块这个,它正好能实现我想要的功能。它允许我通过任何字符(在我的情况下是'\r')来分隔stdout缓冲区,并触发一个新的stdout事件来处理数据。就像...
var splitter = process.stdout.pipe(StreamSplitter("\r"));

splitter.on('token', function (data) {
    console.log("OUT: " + data);
});

splitter.on('done', function (data) {
    console.log("DONE: " + data);
});

1
我已经为此编写了一个自定义流变换子类。优点是您还可以使用子进程喜欢的名称标记行,并且它可以正确处理给定的\r。请随意尝试我的实现:(使用TypeScript编写,但您可以轻松删除所有类型内容)
import { ChildProcessWithoutNullStreams } from "child_process";
import { Transform } from "stream";

export default class LineTagTransform extends Transform {
  lastLineData = '';
  tag = '';

  constructor(tag?: string) {
    super({ objectMode: true });

    this.tag = tag || '';
    if (tag && !tag.endsWith(' ')) this.tag += ' ';
  }

  _transform(chunk: Buffer | string | any, encoding: string, callback: Function) {
    let data: string = chunk.toString().replace(/\r(?!\n)/, '\n\r');
    if (this.lastLineData) data = this.lastLineData + data;

    let lines = data.split(/\r?\n/);
    this.lastLineData = lines.splice(lines.length - 1, 1)[0];

    for (const line of lines) {
      if (line.startsWith('\r')) {
        this.push(`\r${this.tag}${line.substring(1)}`);
      } else {
        this.push(`\n${this.tag}${line}`)
      }
    }
    callback();
  }

  _flush(callback: Function) {
    if (this.lastLineData) {
      if (this.lastLineData.startsWith('\r')) {
        this.push(`\r${this.tag}${this.lastLineData.substring(1)}`);
      } else {
        this.push(`\n${this.tag}${this.lastLineData}`)
      }
    }
    this.lastLineData = '';
    callback();
  }

  static wrapStreams(child: ChildProcessWithoutNullStreams, tag?: string, stdout: NodeJS.WriteStream = process.stdout, stderr: NodeJS.WriteStream = process.stderr) {
    child.stdout.pipe(new LineTagTransform(tag)).pipe(stdout);
    child.stderr.pipe(new LineTagTransform(tag)).pipe(stderr);
  }
}

然后最简单的使用方法是:
const child = spawn('./DownloadUnicorn.exe', options);
LineTagTransform.wrapStreams(child, '[unicorn]');

输出:

[unicorn] Start DownloadUnicorn!
[unicorn] Downloading [=====-----] 50%
...

在单行上使用下载进度条动画! \o/

希望有所帮助! ;)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接