Child_process 处理带回车符的 STDOUT 流 return (\r)
Child_process handling a STDOUT stream with carriage return (\r)
我正在编写一个简单的(大概)应用程序,它允许工作中的内部系统请求从远程服务器到另一个使用 REST 调用发起的远程服务器的复制过程(使用 rsync)。
我对 express 框架已经足够熟悉了,刚刚开始尝试使用 child_process 库,偶然发现了一个小问题。
我正在使用节点的 childProcess.spawn()
成功启动 rsync 进程,我的问题是 rsync 输出其进度行缓冲带回车符 return (\r) 而不是换行符 (\n) .因此,STDOUT 事件 process.stdout.on('data', {})
仅在表示它正在设置传输之前调用一次,然后在复制完成后调用,因为 STDOUT 数据未在运输 return 上刷新,因为当作业完成时,进度更新,只有一个换行符。
最新版本的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 发生时将流通过转换以刷新?然后我可以对该行进行正则表达式并再次提供进度更新。
如果做不到这一点,我想我唯一的其他选择是生成另一个进程来监视不断增长的文件?
非常感谢help/advise。
我发现 this 非常好的模块,它完全可以实现我想要实现的目标。允许我用任何字符分隔 stdout 缓冲区(在我的例子中是 '\r')并触发一个新的 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);
});
我为此创建了一个自定义流 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! ;)
我正在编写一个简单的(大概)应用程序,它允许工作中的内部系统请求从远程服务器到另一个使用 REST 调用发起的远程服务器的复制过程(使用 rsync)。
我对 express 框架已经足够熟悉了,刚刚开始尝试使用 child_process 库,偶然发现了一个小问题。
我正在使用节点的 childProcess.spawn()
成功启动 rsync 进程,我的问题是 rsync 输出其进度行缓冲带回车符 return (\r) 而不是换行符 (\n) .因此,STDOUT 事件 process.stdout.on('data', {})
仅在表示它正在设置传输之前调用一次,然后在复制完成后调用,因为 STDOUT 数据未在运输 return 上刷新,因为当作业完成时,进度更新,只有一个换行符。
最新版本的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 发生时将流通过转换以刷新?然后我可以对该行进行正则表达式并再次提供进度更新。
如果做不到这一点,我想我唯一的其他选择是生成另一个进程来监视不断增长的文件?
非常感谢help/advise。
我发现 this 非常好的模块,它完全可以实现我想要实现的目标。允许我用任何字符分隔 stdout 缓冲区(在我的例子中是 '\r')并触发一个新的 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);
});
我为此创建了一个自定义流 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! ;)