【问题标题】:Child_process handling a STDOUT stream with carriage return (\r)Child_process 处理带有回车符 (\r) 的 STDOUT 流
【发布时间】:2015-05-19 20:52:17
【问题描述】:

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

我对 express 框架已经足够熟悉了,刚刚开始尝试 child_process 库,偶然发现了一个小问题。

我正在使用节点的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

所以你可以看到 stdout.on('data,) 只在 rsync 输出一个新行(有一个 'OUT:' 的地方)时被调用。

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

如果做不到这一点,我想我唯一的其他选择是生成另一个进程来监控正在增长的文件?

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

【问题讨论】:

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


    【解决方案1】:

    我发现this 非常好的模块,它完全符合我的目标。允许我用任何字符(在我的情况下为“\r”)分隔标准输出缓冲区并触发一个新的标准输出事件来处理数据。喜欢……

    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);
    });
    

    【讨论】:

      【解决方案2】:

      为此,我制作了一个自定义流 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! ;)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-13
        • 1970-01-01
        • 2023-03-10
        相关资源
        最近更新 更多