解决 exec 的中文乱码问题

解决 exec 的中文乱码问题

·

1 min read

前情提要

这是我前段时间遇到的问题,当时用 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: ���

原因

原因还是和编码有关,execspawn 的高级版本,他会把 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');