使用Node的child_process时遇到标准输出缓冲问题

126

我想使用Node.js的child_process执行curl命令,从本地网络中共享的文件夹获取一个JSON文件(约220Ko),但实际上返回了一个我无法解决的缓冲区问题。

这是我的代码:

var exec = require('child_process').exec;

var execute = function(command, callback){
    exec(command, function(error, stdout, stderr){ callback(error, stdout); });
};

execute("curl http://" + ip + "/file.json", function(err, json, outerr) {
    if(err) throw err;
    console.log(json);
})

以下是我遇到的错误:

if(err) throw err;
          ^
Error: stdout maxBuffer exceeded.
    at Socket.<anonymous> (child_process.js:678:13)
    at Socket.EventEmitter.emit (events.js:95:17)
    at Socket.<anonymous> (_stream_readable.js:746:14)
    at Socket.EventEmitter.emit (events.js:92:17)
    at emitReadable_ (_stream_readable.js:408:10)
    at emitReadable (_stream_readable.js:404:5)
    at readableAddChunk (_stream_readable.js:165:9)
    at Socket.Readable.push (_stream_readable.js:127:10)
    at Pipe.onread (net.js:526:21)
4个回答

204

当使用child_process.exec时,您需要使用和设置maxBuffer选项。根据文档

maxBuffer指定stdout或stderr上允许的最大数据量-如果超过此值,则子进程将被终止。

文档还指出,maxBuffer的默认值为200KB。

例如,在以下代码中将最大缓冲区大小增加到500KB:

var execute = function(command, callback){
    exec(command, {maxBuffer: 1024 * 500}, function(error, stdout, stderr){ callback(error, stdout); });
};

此外,您可能希望阅读有关http.get的内容,以查看它是否能够实现您尝试执行的操作。


这解决了我的问题,谢谢!共享文件夹实际上是在需要摘要认证的WebDAV协议下,这就是为什么我使用curl来处理它,非常容易使用curl --digest http://login:password@" + ip + "/webdav/file.json - Yonnaled
2
这个默认值太小了,这是第二次我被这个问题难以发现的方式所困扰。 - jlh
6
默认值现在为1MB @jlh。https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback - Carlos
3
一个小技巧。如果你设置 {maxBuffer: undefined},那么缓冲区大小就没有限制了。在内部,child_process 会在命令产生输出时分配小的缓冲区块,因此它从不预先分配 maxBuffer 大小的缓冲区,而是根据需要增长。maxBuffer 只是作为一个故障保护机制来停止输出过多的进程。undefined 恰好可以通过验证检查,并且也规避了故障保护机制。这并没有在任何地方记录,并且可能随时停止工作。请谨慎使用。 - Allon Guralnek
明确一点:maxBuffer 是一个副本。操作系统根据自己的规则为 stdout 分配了一个内部缓冲区。如果 proc 要超出该缓冲区的容量,它将被挂起。假设满足了 Node.js 发出要求(我相信只要缓冲区中有任何一个简单的换行符?),subprocess.stdout.on 将获取缓冲区的一部分,将其发送到处理程序,并且释放该部分,让操作系统恢复正在产生的子进程。我相信 stdout 的 C API 要求它只是一个 cbuf[],具有一些索引,作为一个通用的环形数组暴露出来。这一切都正确吗? - Groostav

67

我遇到了类似的问题,将exec更改为spawn后解决了:

var child = process.spawn('<process>', [<arg1>, <arg2>]);

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

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

child.on('close', function (code) {
    console.log('child process exited with code ' + code);
});

https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options - cs01
3
这个答案不一定是最合适的。我认为问题中的控制台输出可能只是一个例子。几乎没有人会将一个200KB的文件提取出来并抛到控制台上。但是,如果在类似CLI工具之类的程序中使用了process.exec,那么是应该切换到spawn的。 - Pavel Gatilov
2
哇... spawn 很酷。它甚至没有使用回调或承诺...只有事件。这对于将标准输出流传输到控制台可能非常有用。@Pavel Gatilov,这正是我们正在做的事情。FFMpeg 每秒钟都会显示进度...这对缓冲区产生了影响。 - Ray Foss
1
这个事件监听器模式可以使用exec来完全替代spawn(只是在exec中不要使用回调函数)。我更喜欢使用exec,因为它不需要将参数拆分成单独的数组。 - derpedy-doo

8

给答案添加一些说明。

exec 命令在将数据发送到父进程之前缓冲数据。它通常适用于生成较小输出的命令。上述错误发生是因为执行命令生成的输出比最大缓冲区大小要大。解决上述问题的一种方法是按 Tim Cooper 的答案所述指定缓冲区大小。

var execute = function(command, callback){
exec(command, {maxBuffer: 1024 * 500}, function(error, stdout, stderr){ 
 callback(error, stdout); });
};

另一个解决方案是使用spawn方法,与exec相比通常更快,并且不会在发送数据之前缓冲数据。它将数据作为流发送,因此不会出现缓冲区大小的问题。以下是Isampaio使用的代码片段。

var child = process.spawn('<process>', [<arg1>, <arg2>]);
child.stdout.on('data', function (data) {
 console.log('stdout: ' + data);
});
child.stderr.on('data', function (data) {
 console.log('stderr: ' + data);
});
child.on('close', function (code) {
 console.log('child process exited with code ' + code);
});

0

最快的解决方案:

如果这一行输出缓冲区:

childProc.stdout.on('data', (data) => console.log(data)); // <Buffer 1b 5b 34 6d>

你只需要将它更改为:
childProc.stdout.on('data', (data) => console.log(`${data}`)); // bla bla bla

JS控制台将编码您的缓冲区。

或者我们可以简单地使用:console.log(data.toString())


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