Child_process使用回车符处理STDOUT流(\ r)

时间:2015-03-17 12:18:36

标签: javascript node.js stream rsync child-process

我正在编写一个简单的(ish)应用程序,它允许内部系统在工作中请求从远程服务器到使用REST调用发起的另一个远程服务器的复制过程(使用rsync)。

我已经熟悉了快速框架,刚刚开始尝试使用child_process库,偶然发现了一个小问题。

我使用节点childProcess.spawn()成功启动rsync进程,我的问题是rsync使用回车符(\ r)而不是换行符(\ n)输出其进度行缓冲。因此,STDOUT事件process.stdout.on('data', {})仅在说出设置传输之前被调用一次,然后在复制完成后,因为STDOUT数据没有在回车上刷新,当进度更新时,只有一个新行,当作业完成时。

最新版本的rsync(3.1.0)中有一个开关将输出缓冲区结尾更改为\ n而不是\ r \ n但不幸的是,我工作的公司很长一段时间都不会采用这个版本。

我以通常的方式产生并阅读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

所以你可以看到stdout.on('数据)仅在rsync输出一个新行时被调用(其中有一个' OUT:')。

我的问题是,我可以更改吗?当\ r发生时,也许将流通过转换来刷新?然后我可以正则行,并再次提供进度更新。

如果不这样做,我想我唯一的另一个选择就是产生另一个进程来监控不断增长的文件?

非常感谢任何帮助/建议。

2 个答案:

答案 0 :(得分:1)

我发现了this非常好的模块,完全符合我的目标。允许我按任何字符(在我的情况下' \ 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 :(得分:0)

为此,我制作了一个自定义流Transform子类。优点是您还可以使用子进程的最爱名称标记行,并以正确的方式处理给定的\r

随意尝试我的实现: (在打字稿中,但您可以轻松删除所有打字稿)

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 /

HTH! ;)