前情提要
这是我前段时间遇到的问题,当时用 exec
跑一个脚本,但脚本的日志有中文,打印出来一堆乱码。
import { exec } from 'child_process';
exec('echo 你好', (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
这是能复现问题的一段简单代码,打印的结果是:stdout: ���
。
原因
原因还是和编码有关,exec
是 spawn
的高级版本,他会把 spawn
得到的原始的 Buffer
数据自动通过 utf-8 进行编码,最后得到字符串 stdout
。
(顺带一提,spawn
会直接把数据以 Buffer
的形式交付,如果直接转成字符串也会出问题)
在 Windows 环境中,这个流程从一开始就有缺陷,Windows CMD 默认使用的是 GBK 编码(cp936),所以 buffer 中的字节数据就是 GBK 格式的,如果用 GBK 的编码规则去读是没问题的,但要把它以 utf-8 的编码规则去读,那肯定会乱码,而 exec
就默认把他当作 utf-8 了。
解法(spawn)
const stream = spawn('echo', ['你好'], {});
spawn
的数据会以流的形式返回,每一段都是 Buffer
,所以可以在确保数据完整后,直接使用 iconv
之类的库,对数据以 GBK 规则读取成 utf-8 字符串:
const output = iconv.decode(stdout, 'cp936');
解法(exec)
对于 exec
得多一个步骤,因为 exec
将原始数据转换成了 utf-8,所以需要先加一个参数,让他保留原始数据:
exec('echo 你好', {
encoding: 'binary'
}, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
此时 stdout
虽然还是乱码,但这是在 utf-8 规则下的显示,实际的数据是原始的(也就是 GBK),然后创建一个 Buffer
去承载他:
Buffer.from(stdout, 'binary');
这里也声明了要按照 binary 去进行解读(也就是保留原样),接着使用 iconv
等库将 GBK 数据读取成 utf-8 字符串:
const buffer = Buffer.from(stdout, 'binary');
const output = iconv.decode(buffer, 'cp936');