Node.js下如何让仅支持文件输出的pico2wave输出到stdout?
我用pico2wave生成音频文件,但这个程序只支持写入文件,不能直接输出到stdout。我创建了一个指向/dev/stdout的符号链接pico2wave.wav,在Shell里执行:
pico2wave -w pico2wave.wav test!
能正常把音频内容输出到stdout。但在Node.js里用child_process.spawn执行相同命令时,却拿不到标准输出的内容:
const pico2wave = spawn("pico2wave", [ '-w', `${path.join(__dirname, 'pico2wave.wav')}`, 'test!', ], { stdio: ['ignore', 'pipe', 'pipe'] }); pico2wave.stderr.on("data", (chunk) => { console.log(chunk.toString()); }); pico2wave.stdout.on('data', (data) => { console.log(data); })
查文档发现:
'pipe':在子进程与父进程之间创建管道。父进程端的管道作为subprocess.stdio[fd]属性暴露给父进程。为文件描述符0、1、2创建的管道也分别可用作subprocess.stdin、subprocess.stdout和subprocess.stderr。这些不是真正的Unix管道,因此子进程无法通过它们的描述符文件(如/dev/fd/2或/dev/stdout)使用它们。
这说明子进程没法用/dev/stdout,导致符号链接的方法失效。我试过用sh命令调用pico2wave,问题还是没解决,求可行方案?
可行解决方案
方法1:临时文件中转
既然pico2wave只能写入文件,先让它写入临时文件,写完后读取文件内容输出到stdout,最后删除临时文件。可以用Node.js的fs模块配合tmp包(需提前安装:npm install tmp):
const spawn = require('child_process').spawn; const fs = require('fs'); const tmp = require('tmp'); // 创建临时文件 tmp.file({ postfix: '.wav' }, (err, tmpPath, fd, cleanupCallback) => { if (err) throw err; // 调用pico2wave写入临时文件 const pico = spawn('pico2wave', ['-w', tmpPath, 'test!']); pico.on('close', (code) => { if (code !== 0) { console.error('pico2wave执行失败'); cleanupCallback(); return; } // 读取临时文件内容并输出到stdout const readStream = fs.createReadStream(tmpPath); readStream.pipe(process.stdout); readStream.on('end', cleanupCallback); // 读完删除临时文件 }); pico.stderr.on('data', (chunk) => { console.error(chunk.toString()); }); });
方法2:利用Shell进程替换(Linux/macOS)
在支持进程替换的Shell(如bash、zsh)中,用>(command)语法创建管道,让pico2wave写入这个管道对应的伪文件,再通过命令把内容输出到stdout。Node.js中开启shell: true执行该命令:
const spawn = require('child_process').spawn; const pico = spawn('pico2wave -w >(cat) test!', { shell: true, stdio: ['ignore', 'pipe', 'pipe'] }); pico.stdout.on('data', (data) => { console.log(data); // 这里拿到音频二进制数据 }); pico.stderr.on('data', (chunk) => { console.error(chunk.toString()); });
原理是>(cat)会创建管道,pico2wave写入管道的写入端,cat从读取端把内容输出到stdout,Node.js通过pipe捕获该内容。
方法3:直接写入父进程的stdout文件描述符(Linux)
如果系统支持/proc/self/fd(大部分Linux发行版都支持),让pico2wave直接写入当前进程的stdout文件描述符/proc/self/fd/1,绕过Node.js的管道限制:
const spawn = require('child_process').spawn; const pico = spawn('pico2wave', ['-w', '/proc/self/fd/1', 'test!'], { stdio: ['ignore', 'inherit', 'pipe'] }); // 子进程stdout已继承父进程stdout,无需监听pico.stdout,仅捕获stderr即可 pico.stderr.on('data', (chunk) => { console.error(chunk.toString()); });
内容的提问来源于stack exchange,提问作者violetflare

